Updated copyright notice to 2014
[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, 2014 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 VariantGrand:
392     flags &= ~F_ALL_CASTLE_OK;
393     break;
394   default:
395     break;
396   }
397   return flags;
398 }
399
400 FILE *gameFileFP, *debugFP, *serverFP;
401 char *currentDebugFile; // [HGM] debug split: to remember name
402
403 /*
404     [AS] Note: sometimes, the sscanf() function is used to parse the input
405     into a fixed-size buffer. Because of this, we must be prepared to
406     receive strings as long as the size of the input buffer, which is currently
407     set to 4K for Windows and 8K for the rest.
408     So, we must either allocate sufficiently large buffers here, or
409     reduce the size of the input buffer in the input reading part.
410 */
411
412 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
413 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
414 char thinkOutput1[MSG_SIZ*10];
415
416 ChessProgramState first, second, pairing;
417
418 /* premove variables */
419 int premoveToX = 0;
420 int premoveToY = 0;
421 int premoveFromX = 0;
422 int premoveFromY = 0;
423 int premovePromoChar = 0;
424 int gotPremove = 0;
425 Boolean alarmSounded;
426 /* end premove variables */
427
428 char *ics_prefix = "$";
429 enum ICS_TYPE ics_type = ICS_GENERIC;
430
431 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
432 int pauseExamForwardMostMove = 0;
433 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
434 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
435 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
436 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
437 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
438 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
439 int whiteFlag = FALSE, blackFlag = FALSE;
440 int userOfferedDraw = FALSE;
441 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
442 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
443 int cmailMoveType[CMAIL_MAX_GAMES];
444 long ics_clock_paused = 0;
445 ProcRef icsPR = NoProc, cmailPR = NoProc;
446 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
447 GameMode gameMode = BeginningOfGame;
448 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
449 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
450 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
451 int hiddenThinkOutputState = 0; /* [AS] */
452 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
453 int adjudicateLossPlies = 6;
454 char white_holding[64], black_holding[64];
455 TimeMark lastNodeCountTime;
456 long lastNodeCount=0;
457 int shiftKey, controlKey; // [HGM] set by mouse handler
458
459 int have_sent_ICS_logon = 0;
460 int movesPerSession;
461 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
462 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
463 Boolean adjustedClock;
464 long timeControl_2; /* [AS] Allow separate time controls */
465 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
466 long timeRemaining[2][MAX_MOVES];
467 int matchGame = 0, nextGame = 0, roundNr = 0;
468 Boolean waitingForGame = FALSE, startingEngine = FALSE;
469 TimeMark programStartTime, pauseStart;
470 char ics_handle[MSG_SIZ];
471 int have_set_title = 0;
472
473 /* animateTraining preserves the state of appData.animate
474  * when Training mode is activated. This allows the
475  * response to be animated when appData.animate == TRUE and
476  * appData.animateDragging == TRUE.
477  */
478 Boolean animateTraining;
479
480 GameInfo gameInfo;
481
482 AppData appData;
483
484 Board boards[MAX_MOVES];
485 /* [HGM] Following 7 needed for accurate legality tests: */
486 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
487 signed char  initialRights[BOARD_FILES];
488 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
489 int   initialRulePlies, FENrulePlies;
490 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
491 int loadFlag = 0;
492 Boolean shuffleOpenings;
493 int mute; // mute all sounds
494
495 // [HGM] vari: next 12 to save and restore variations
496 #define MAX_VARIATIONS 10
497 int framePtr = MAX_MOVES-1; // points to free stack entry
498 int storedGames = 0;
499 int savedFirst[MAX_VARIATIONS];
500 int savedLast[MAX_VARIATIONS];
501 int savedFramePtr[MAX_VARIATIONS];
502 char *savedDetails[MAX_VARIATIONS];
503 ChessMove savedResult[MAX_VARIATIONS];
504
505 void PushTail P((int firstMove, int lastMove));
506 Boolean PopTail P((Boolean annotate));
507 void PushInner P((int firstMove, int lastMove));
508 void PopInner P((Boolean annotate));
509 void CleanupTail P((void));
510
511 ChessSquare  FIDEArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackBishop, BlackKnight, BlackRook }
516 };
517
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackKing, BlackKnight, BlackRook }
523 };
524
525 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528     { BlackRook, BlackMan, BlackBishop, BlackQueen,
529         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
530 };
531
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
537 };
538
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
544 };
545
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
551 };
552
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackMan, BlackFerz,
557         BlackKing, BlackMan, BlackKnight, BlackRook }
558 };
559
560
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
567 };
568
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
581 };
582
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
588 };
589
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
595 };
596
597 ChessSquare GrandArray[2][BOARD_FILES] = {
598     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
599         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
600     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
601         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
602 };
603
604 #ifdef GOTHIC
605 ChessSquare GothicArray[2][BOARD_FILES] = {
606     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
607         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
608     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
609         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
610 };
611 #else // !GOTHIC
612 #define GothicArray CapablancaArray
613 #endif // !GOTHIC
614
615 #ifdef FALCON
616 ChessSquare FalconArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
618         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
620         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
621 };
622 #else // !FALCON
623 #define FalconArray CapablancaArray
624 #endif // !FALCON
625
626 #else // !(BOARD_FILES>=10)
627 #define XiangqiPosition FIDEArray
628 #define CapablancaArray FIDEArray
629 #define GothicArray FIDEArray
630 #define GreatArray FIDEArray
631 #endif // !(BOARD_FILES>=10)
632
633 #if (BOARD_FILES>=12)
634 ChessSquare CourierArray[2][BOARD_FILES] = {
635     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
636         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
637     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
638         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
639 };
640 #else // !(BOARD_FILES>=12)
641 #define CourierArray CapablancaArray
642 #endif // !(BOARD_FILES>=12)
643
644
645 Board initialPosition;
646
647
648 /* Convert str to a rating. Checks for special cases of "----",
649
650    "++++", etc. Also strips ()'s */
651 int
652 string_to_rating (char *str)
653 {
654   while(*str && !isdigit(*str)) ++str;
655   if (!*str)
656     return 0;   /* One of the special "no rating" cases */
657   else
658     return atoi(str);
659 }
660
661 void
662 ClearProgramStats ()
663 {
664     /* Init programStats */
665     programStats.movelist[0] = 0;
666     programStats.depth = 0;
667     programStats.nr_moves = 0;
668     programStats.moves_left = 0;
669     programStats.nodes = 0;
670     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
671     programStats.score = 0;
672     programStats.got_only_move = 0;
673     programStats.got_fail = 0;
674     programStats.line_is_book = 0;
675 }
676
677 void
678 CommonEngineInit ()
679 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
680     if (appData.firstPlaysBlack) {
681         first.twoMachinesColor = "black\n";
682         second.twoMachinesColor = "white\n";
683     } else {
684         first.twoMachinesColor = "white\n";
685         second.twoMachinesColor = "black\n";
686     }
687
688     first.other = &second;
689     second.other = &first;
690
691     { float norm = 1;
692         if(appData.timeOddsMode) {
693             norm = appData.timeOdds[0];
694             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
695         }
696         first.timeOdds  = appData.timeOdds[0]/norm;
697         second.timeOdds = appData.timeOdds[1]/norm;
698     }
699
700     if(programVersion) free(programVersion);
701     if (appData.noChessProgram) {
702         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
703         sprintf(programVersion, "%s", PACKAGE_STRING);
704     } else {
705       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
706       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
707       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708     }
709 }
710
711 void
712 UnloadEngine (ChessProgramState *cps)
713 {
714         /* Kill off first chess program */
715         if (cps->isr != NULL)
716           RemoveInputSource(cps->isr);
717         cps->isr = NULL;
718
719         if (cps->pr != NoProc) {
720             ExitAnalyzeMode();
721             DoSleep( appData.delayBeforeQuit );
722             SendToProgram("quit\n", cps);
723             DoSleep( appData.delayAfterQuit );
724             DestroyChildProcess(cps->pr, cps->useSigterm);
725         }
726         cps->pr = NoProc;
727         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 }
729
730 void
731 ClearOptions (ChessProgramState *cps)
732 {
733     int i;
734     cps->nrOptions = cps->comboCnt = 0;
735     for(i=0; i<MAX_OPTIONS; i++) {
736         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
737         cps->option[i].textValue = 0;
738     }
739 }
740
741 char *engineNames[] = {
742   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
743      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 N_("first"),
745   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
746      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
747 N_("second")
748 };
749
750 void
751 InitEngine (ChessProgramState *cps, int n)
752 {   // [HGM] all engine initialiation put in a function that does one engine
753
754     ClearOptions(cps);
755
756     cps->which = engineNames[n];
757     cps->maybeThinking = FALSE;
758     cps->pr = NoProc;
759     cps->isr = NULL;
760     cps->sendTime = 2;
761     cps->sendDrawOffers = 1;
762
763     cps->program = appData.chessProgram[n];
764     cps->host = appData.host[n];
765     cps->dir = appData.directory[n];
766     cps->initString = appData.engInitString[n];
767     cps->computerString = appData.computerString[n];
768     cps->useSigint  = TRUE;
769     cps->useSigterm = TRUE;
770     cps->reuse = appData.reuse[n];
771     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
772     cps->useSetboard = FALSE;
773     cps->useSAN = FALSE;
774     cps->usePing = FALSE;
775     cps->lastPing = 0;
776     cps->lastPong = 0;
777     cps->usePlayother = FALSE;
778     cps->useColors = TRUE;
779     cps->useUsermove = FALSE;
780     cps->sendICS = FALSE;
781     cps->sendName = appData.icsActive;
782     cps->sdKludge = FALSE;
783     cps->stKludge = FALSE;
784     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
785     TidyProgramName(cps->program, cps->host, cps->tidy);
786     cps->matchWins = 0;
787     ASSIGN(cps->variants, appData.variant);
788     cps->analysisSupport = 2; /* detect */
789     cps->analyzing = FALSE;
790     cps->initDone = FALSE;
791     cps->reload = FALSE;
792
793     /* New features added by Tord: */
794     cps->useFEN960 = FALSE;
795     cps->useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     cps->fenOverride  = appData.fenOverride[n];
798
799     /* [HGM] time odds: set factor for each machine */
800     cps->timeOdds  = appData.timeOdds[n];
801
802     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
803     cps->accumulateTC = appData.accumulateTC[n];
804     cps->maxNrOfSessions = 1;
805
806     /* [HGM] debug */
807     cps->debug = FALSE;
808
809     cps->supportsNPS = UNKNOWN;
810     cps->memSize = FALSE;
811     cps->maxCores = FALSE;
812     ASSIGN(cps->egtFormats, "");
813
814     /* [HGM] options */
815     cps->optionSettings  = appData.engOptions[n];
816
817     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
818     cps->isUCI = appData.isUCI[n]; /* [AS] */
819     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
820
821     if (appData.protocolVersion[n] > PROTOVER
822         || appData.protocolVersion[n] < 1)
823       {
824         char buf[MSG_SIZ];
825         int len;
826
827         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
828                        appData.protocolVersion[n]);
829         if( (len >= MSG_SIZ) && appData.debugMode )
830           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
831
832         DisplayFatalError(buf, 0, 2);
833       }
834     else
835       {
836         cps->protocolVersion = appData.protocolVersion[n];
837       }
838
839     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
840     ParseFeatures(appData.featureDefaults, cps);
841 }
842
843 ChessProgramState *savCps;
844
845 GameMode oldMode;
846
847 void
848 LoadEngine ()
849 {
850     int i;
851     if(WaitForEngine(savCps, LoadEngine)) return;
852     CommonEngineInit(); // recalculate time odds
853     if(gameInfo.variant != StringToVariant(appData.variant)) {
854         // we changed variant when loading the engine; this forces us to reset
855         Reset(TRUE, savCps != &first);
856         oldMode = BeginningOfGame; // to prevent restoring old mode
857     }
858     InitChessProgram(savCps, FALSE);
859     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
860     DisplayMessage("", "");
861     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
862     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
863     ThawUI();
864     SetGNUMode();
865     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
866 }
867
868 void
869 ReplaceEngine (ChessProgramState *cps, int n)
870 {
871     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
872     keepInfo = 1;
873     if(oldMode != BeginningOfGame) EditGameEvent();
874     keepInfo = 0;
875     UnloadEngine(cps);
876     appData.noChessProgram = FALSE;
877     appData.clockMode = TRUE;
878     InitEngine(cps, n);
879     UpdateLogos(TRUE);
880     if(n) return; // only startup first engine immediately; second can wait
881     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
882     LoadEngine();
883 }
884
885 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
886 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
887
888 static char resetOptions[] =
889         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
890         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
891         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
892         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
893
894 void
895 FloatToFront(char **list, char *engineLine)
896 {
897     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
898     int i=0;
899     if(appData.recentEngines <= 0) return;
900     TidyProgramName(engineLine, "localhost", tidy+1);
901     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
902     strncpy(buf+1, *list, MSG_SIZ-50);
903     if(p = strstr(buf, tidy)) { // tidy name appears in list
904         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
905         while(*p++ = *++q); // squeeze out
906     }
907     strcat(tidy, buf+1); // put list behind tidy name
908     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
909     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
910     ASSIGN(*list, tidy+1);
911 }
912
913 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
914
915 void
916 Load (ChessProgramState *cps, int i)
917 {
918     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
919     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
920         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
921         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
922         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
923         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
924         appData.firstProtocolVersion = PROTOVER;
925         ParseArgsFromString(buf);
926         SwapEngines(i);
927         ReplaceEngine(cps, i);
928         FloatToFront(&appData.recentEngineList, engineLine);
929         return;
930     }
931     p = engineName;
932     while(q = strchr(p, SLASH)) p = q+1;
933     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
934     if(engineDir[0] != NULLCHAR) {
935         ASSIGN(appData.directory[i], engineDir); p = engineName;
936     } else if(p != engineName) { // derive directory from engine path, when not given
937         p[-1] = 0;
938         ASSIGN(appData.directory[i], engineName);
939         p[-1] = SLASH;
940         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
941     } else { ASSIGN(appData.directory[i], "."); }
942     if(params[0]) {
943         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
944         snprintf(command, MSG_SIZ, "%s %s", p, params);
945         p = command;
946     }
947     ASSIGN(appData.chessProgram[i], p);
948     appData.isUCI[i] = isUCI;
949     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
950     appData.hasOwnBookUCI[i] = hasBook;
951     if(!nickName[0]) useNick = FALSE;
952     if(useNick) ASSIGN(appData.pgnName[i], nickName);
953     if(addToList) {
954         int len;
955         char quote;
956         q = firstChessProgramNames;
957         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
958         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
959         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
960                         quote, p, quote, appData.directory[i],
961                         useNick ? " -fn \"" : "",
962                         useNick ? nickName : "",
963                         useNick ? "\"" : "",
964                         v1 ? " -firstProtocolVersion 1" : "",
965                         hasBook ? "" : " -fNoOwnBookUCI",
966                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
967                         storeVariant ? " -variant " : "",
968                         storeVariant ? VariantName(gameInfo.variant) : "");
969         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
970         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
971         if(insert != q) insert[-1] = NULLCHAR;
972         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
973         if(q)   free(q);
974         FloatToFront(&appData.recentEngineList, buf);
975     }
976     ReplaceEngine(cps, i);
977 }
978
979 void
980 InitTimeControls ()
981 {
982     int matched, min, sec;
983     /*
984      * Parse timeControl resource
985      */
986     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
987                           appData.movesPerSession)) {
988         char buf[MSG_SIZ];
989         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
990         DisplayFatalError(buf, 0, 2);
991     }
992
993     /*
994      * Parse searchTime resource
995      */
996     if (*appData.searchTime != NULLCHAR) {
997         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
998         if (matched == 1) {
999             searchTime = min * 60;
1000         } else if (matched == 2) {
1001             searchTime = min * 60 + sec;
1002         } else {
1003             char buf[MSG_SIZ];
1004             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1005             DisplayFatalError(buf, 0, 2);
1006         }
1007     }
1008 }
1009
1010 void
1011 InitBackEnd1 ()
1012 {
1013
1014     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1015     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1016
1017     GetTimeMark(&programStartTime);
1018     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1019     appData.seedBase = random() + (random()<<15);
1020     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1021
1022     ClearProgramStats();
1023     programStats.ok_to_send = 1;
1024     programStats.seen_stat = 0;
1025
1026     /*
1027      * Initialize game list
1028      */
1029     ListNew(&gameList);
1030
1031
1032     /*
1033      * Internet chess server status
1034      */
1035     if (appData.icsActive) {
1036         appData.matchMode = FALSE;
1037         appData.matchGames = 0;
1038 #if ZIPPY
1039         appData.noChessProgram = !appData.zippyPlay;
1040 #else
1041         appData.zippyPlay = FALSE;
1042         appData.zippyTalk = FALSE;
1043         appData.noChessProgram = TRUE;
1044 #endif
1045         if (*appData.icsHelper != NULLCHAR) {
1046             appData.useTelnet = TRUE;
1047             appData.telnetProgram = appData.icsHelper;
1048         }
1049     } else {
1050         appData.zippyTalk = appData.zippyPlay = FALSE;
1051     }
1052
1053     /* [AS] Initialize pv info list [HGM] and game state */
1054     {
1055         int i, j;
1056
1057         for( i=0; i<=framePtr; i++ ) {
1058             pvInfoList[i].depth = -1;
1059             boards[i][EP_STATUS] = EP_NONE;
1060             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1061         }
1062     }
1063
1064     InitTimeControls();
1065
1066     /* [AS] Adjudication threshold */
1067     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1068
1069     InitEngine(&first, 0);
1070     InitEngine(&second, 1);
1071     CommonEngineInit();
1072
1073     pairing.which = "pairing"; // pairing engine
1074     pairing.pr = NoProc;
1075     pairing.isr = NULL;
1076     pairing.program = appData.pairingEngine;
1077     pairing.host = "localhost";
1078     pairing.dir = ".";
1079
1080     if (appData.icsActive) {
1081         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1082     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1083         appData.clockMode = FALSE;
1084         first.sendTime = second.sendTime = 0;
1085     }
1086
1087 #if ZIPPY
1088     /* Override some settings from environment variables, for backward
1089        compatibility.  Unfortunately it's not feasible to have the env
1090        vars just set defaults, at least in xboard.  Ugh.
1091     */
1092     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1093       ZippyInit();
1094     }
1095 #endif
1096
1097     if (!appData.icsActive) {
1098       char buf[MSG_SIZ];
1099       int len;
1100
1101       /* Check for variants that are supported only in ICS mode,
1102          or not at all.  Some that are accepted here nevertheless
1103          have bugs; see comments below.
1104       */
1105       VariantClass variant = StringToVariant(appData.variant);
1106       switch (variant) {
1107       case VariantBughouse:     /* need four players and two boards */
1108       case VariantKriegspiel:   /* need to hide pieces and move details */
1109         /* case VariantFischeRandom: (Fabien: moved below) */
1110         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1111         if( (len >= MSG_SIZ) && appData.debugMode )
1112           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1113
1114         DisplayFatalError(buf, 0, 2);
1115         return;
1116
1117       case VariantUnknown:
1118       case VariantLoadable:
1119       case Variant29:
1120       case Variant30:
1121       case Variant31:
1122       case Variant32:
1123       case Variant33:
1124       case Variant34:
1125       case Variant35:
1126       case Variant36:
1127       default:
1128         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1129         if( (len >= MSG_SIZ) && appData.debugMode )
1130           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1131
1132         DisplayFatalError(buf, 0, 2);
1133         return;
1134
1135       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1136       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1137       case VariantGothic:     /* [HGM] should work */
1138       case VariantCapablanca: /* [HGM] should work */
1139       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1140       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1141       case VariantKnightmate: /* [HGM] should work */
1142       case VariantCylinder:   /* [HGM] untested */
1143       case VariantFalcon:     /* [HGM] untested */
1144       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1145                                  offboard interposition not understood */
1146       case VariantNormal:     /* definitely works! */
1147       case VariantWildCastle: /* pieces not automatically shuffled */
1148       case VariantNoCastle:   /* pieces not automatically shuffled */
1149       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1150       case VariantLosers:     /* should work except for win condition,
1151                                  and doesn't know captures are mandatory */
1152       case VariantSuicide:    /* should work except for win condition,
1153                                  and doesn't know captures are mandatory */
1154       case VariantGiveaway:   /* should work except for win condition,
1155                                  and doesn't know captures are mandatory */
1156       case VariantTwoKings:   /* should work */
1157       case VariantAtomic:     /* should work except for win condition */
1158       case Variant3Check:     /* should work except for win condition */
1159       case VariantShatranj:   /* should work except for all win conditions */
1160       case VariantMakruk:     /* should work except for draw countdown */
1161       case VariantBerolina:   /* might work if TestLegality is off */
1162       case VariantCapaRandom: /* should work */
1163       case VariantJanus:      /* should work */
1164       case VariantSuper:      /* experimental */
1165       case VariantGreat:      /* experimental, requires legality testing to be off */
1166       case VariantSChess:     /* S-Chess, should work */
1167       case VariantGrand:      /* should work */
1168       case VariantSpartan:    /* should work */
1169         break;
1170       }
1171     }
1172
1173 }
1174
1175 int
1176 NextIntegerFromString (char ** str, long * value)
1177 {
1178     int result = -1;
1179     char * s = *str;
1180
1181     while( *s == ' ' || *s == '\t' ) {
1182         s++;
1183     }
1184
1185     *value = 0;
1186
1187     if( *s >= '0' && *s <= '9' ) {
1188         while( *s >= '0' && *s <= '9' ) {
1189             *value = *value * 10 + (*s - '0');
1190             s++;
1191         }
1192
1193         result = 0;
1194     }
1195
1196     *str = s;
1197
1198     return result;
1199 }
1200
1201 int
1202 NextTimeControlFromString (char ** str, long * value)
1203 {
1204     long temp;
1205     int result = NextIntegerFromString( str, &temp );
1206
1207     if( result == 0 ) {
1208         *value = temp * 60; /* Minutes */
1209         if( **str == ':' ) {
1210             (*str)++;
1211             result = NextIntegerFromString( str, &temp );
1212             *value += temp; /* Seconds */
1213         }
1214     }
1215
1216     return result;
1217 }
1218
1219 int
1220 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1221 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1222     int result = -1, type = 0; long temp, temp2;
1223
1224     if(**str != ':') return -1; // old params remain in force!
1225     (*str)++;
1226     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1227     if( NextIntegerFromString( str, &temp ) ) return -1;
1228     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1229
1230     if(**str != '/') {
1231         /* time only: incremental or sudden-death time control */
1232         if(**str == '+') { /* increment follows; read it */
1233             (*str)++;
1234             if(**str == '!') type = *(*str)++; // Bronstein TC
1235             if(result = NextIntegerFromString( str, &temp2)) return -1;
1236             *inc = temp2 * 1000;
1237             if(**str == '.') { // read fraction of increment
1238                 char *start = ++(*str);
1239                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1240                 temp2 *= 1000;
1241                 while(start++ < *str) temp2 /= 10;
1242                 *inc += temp2;
1243             }
1244         } else *inc = 0;
1245         *moves = 0; *tc = temp * 1000; *incType = type;
1246         return 0;
1247     }
1248
1249     (*str)++; /* classical time control */
1250     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1251
1252     if(result == 0) {
1253         *moves = temp;
1254         *tc    = temp2 * 1000;
1255         *inc   = 0;
1256         *incType = type;
1257     }
1258     return result;
1259 }
1260
1261 int
1262 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1263 {   /* [HGM] get time to add from the multi-session time-control string */
1264     int incType, moves=1; /* kludge to force reading of first session */
1265     long time, increment;
1266     char *s = tcString;
1267
1268     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1269     do {
1270         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1271         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1272         if(movenr == -1) return time;    /* last move before new session     */
1273         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1274         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1275         if(!moves) return increment;     /* current session is incremental   */
1276         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1277     } while(movenr >= -1);               /* try again for next session       */
1278
1279     return 0; // no new time quota on this move
1280 }
1281
1282 int
1283 ParseTimeControl (char *tc, float ti, int mps)
1284 {
1285   long tc1;
1286   long tc2;
1287   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1288   int min, sec=0;
1289
1290   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1291   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1292       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1293   if(ti > 0) {
1294
1295     if(mps)
1296       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1297     else
1298       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1299   } else {
1300     if(mps)
1301       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1302     else
1303       snprintf(buf, MSG_SIZ, ":%s", mytc);
1304   }
1305   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1306
1307   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1308     return FALSE;
1309   }
1310
1311   if( *tc == '/' ) {
1312     /* Parse second time control */
1313     tc++;
1314
1315     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1316       return FALSE;
1317     }
1318
1319     if( tc2 == 0 ) {
1320       return FALSE;
1321     }
1322
1323     timeControl_2 = tc2 * 1000;
1324   }
1325   else {
1326     timeControl_2 = 0;
1327   }
1328
1329   if( tc1 == 0 ) {
1330     return FALSE;
1331   }
1332
1333   timeControl = tc1 * 1000;
1334
1335   if (ti >= 0) {
1336     timeIncrement = ti * 1000;  /* convert to ms */
1337     movesPerSession = 0;
1338   } else {
1339     timeIncrement = 0;
1340     movesPerSession = mps;
1341   }
1342   return TRUE;
1343 }
1344
1345 void
1346 InitBackEnd2 ()
1347 {
1348     if (appData.debugMode) {
1349 #    ifdef __GIT_VERSION
1350       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1351 #    else
1352       fprintf(debugFP, "Version: %s\n", programVersion);
1353 #    endif
1354     }
1355     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1356
1357     set_cont_sequence(appData.wrapContSeq);
1358     if (appData.matchGames > 0) {
1359         appData.matchMode = TRUE;
1360     } else if (appData.matchMode) {
1361         appData.matchGames = 1;
1362     }
1363     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1364         appData.matchGames = appData.sameColorGames;
1365     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1366         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1367         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1368     }
1369     Reset(TRUE, FALSE);
1370     if (appData.noChessProgram || first.protocolVersion == 1) {
1371       InitBackEnd3();
1372     } else {
1373       /* kludge: allow timeout for initial "feature" commands */
1374       FreezeUI();
1375       DisplayMessage("", _("Starting chess program"));
1376       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1377     }
1378 }
1379
1380 int
1381 CalculateIndex (int index, int gameNr)
1382 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1383     int res;
1384     if(index > 0) return index; // fixed nmber
1385     if(index == 0) return 1;
1386     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1387     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1388     return res;
1389 }
1390
1391 int
1392 LoadGameOrPosition (int gameNr)
1393 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1394     if (*appData.loadGameFile != NULLCHAR) {
1395         if (!LoadGameFromFile(appData.loadGameFile,
1396                 CalculateIndex(appData.loadGameIndex, gameNr),
1397                               appData.loadGameFile, FALSE)) {
1398             DisplayFatalError(_("Bad game file"), 0, 1);
1399             return 0;
1400         }
1401     } else if (*appData.loadPositionFile != NULLCHAR) {
1402         if (!LoadPositionFromFile(appData.loadPositionFile,
1403                 CalculateIndex(appData.loadPositionIndex, gameNr),
1404                                   appData.loadPositionFile)) {
1405             DisplayFatalError(_("Bad position file"), 0, 1);
1406             return 0;
1407         }
1408     }
1409     return 1;
1410 }
1411
1412 void
1413 ReserveGame (int gameNr, char resChar)
1414 {
1415     FILE *tf = fopen(appData.tourneyFile, "r+");
1416     char *p, *q, c, buf[MSG_SIZ];
1417     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1418     safeStrCpy(buf, lastMsg, MSG_SIZ);
1419     DisplayMessage(_("Pick new game"), "");
1420     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1421     ParseArgsFromFile(tf);
1422     p = q = appData.results;
1423     if(appData.debugMode) {
1424       char *r = appData.participants;
1425       fprintf(debugFP, "results = '%s'\n", p);
1426       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1427       fprintf(debugFP, "\n");
1428     }
1429     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1430     nextGame = q - p;
1431     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1432     safeStrCpy(q, p, strlen(p) + 2);
1433     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1434     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1435     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1436         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1437         q[nextGame] = '*';
1438     }
1439     fseek(tf, -(strlen(p)+4), SEEK_END);
1440     c = fgetc(tf);
1441     if(c != '"') // depending on DOS or Unix line endings we can be one off
1442          fseek(tf, -(strlen(p)+2), SEEK_END);
1443     else fseek(tf, -(strlen(p)+3), SEEK_END);
1444     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1445     DisplayMessage(buf, "");
1446     free(p); appData.results = q;
1447     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1448        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1449       int round = appData.defaultMatchGames * appData.tourneyType;
1450       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1451          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1452         UnloadEngine(&first);  // next game belongs to other pairing;
1453         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1454     }
1455     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1456 }
1457
1458 void
1459 MatchEvent (int mode)
1460 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1461         int dummy;
1462         if(matchMode) { // already in match mode: switch it off
1463             abortMatch = TRUE;
1464             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1465             return;
1466         }
1467 //      if(gameMode != BeginningOfGame) {
1468 //          DisplayError(_("You can only start a match from the initial position."), 0);
1469 //          return;
1470 //      }
1471         abortMatch = FALSE;
1472         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1473         /* Set up machine vs. machine match */
1474         nextGame = 0;
1475         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1476         if(appData.tourneyFile[0]) {
1477             ReserveGame(-1, 0);
1478             if(nextGame > appData.matchGames) {
1479                 char buf[MSG_SIZ];
1480                 if(strchr(appData.results, '*') == NULL) {
1481                     FILE *f;
1482                     appData.tourneyCycles++;
1483                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1484                         fclose(f);
1485                         NextTourneyGame(-1, &dummy);
1486                         ReserveGame(-1, 0);
1487                         if(nextGame <= appData.matchGames) {
1488                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1489                             matchMode = mode;
1490                             ScheduleDelayedEvent(NextMatchGame, 10000);
1491                             return;
1492                         }
1493                     }
1494                 }
1495                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1496                 DisplayError(buf, 0);
1497                 appData.tourneyFile[0] = 0;
1498                 return;
1499             }
1500         } else
1501         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1502             DisplayFatalError(_("Can't have a match with no chess programs"),
1503                               0, 2);
1504             return;
1505         }
1506         matchMode = mode;
1507         matchGame = roundNr = 1;
1508         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1509         NextMatchGame();
1510 }
1511
1512 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1513
1514 void
1515 InitBackEnd3 P((void))
1516 {
1517     GameMode initialMode;
1518     char buf[MSG_SIZ];
1519     int err, len;
1520
1521     InitChessProgram(&first, startedFromSetupPosition);
1522
1523     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1524         free(programVersion);
1525         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1526         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1527         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1528     }
1529
1530     if (appData.icsActive) {
1531 #ifdef WIN32
1532         /* [DM] Make a console window if needed [HGM] merged ifs */
1533         ConsoleCreate();
1534 #endif
1535         err = establish();
1536         if (err != 0)
1537           {
1538             if (*appData.icsCommPort != NULLCHAR)
1539               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1540                              appData.icsCommPort);
1541             else
1542               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1543                         appData.icsHost, appData.icsPort);
1544
1545             if( (len >= MSG_SIZ) && appData.debugMode )
1546               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1547
1548             DisplayFatalError(buf, err, 1);
1549             return;
1550         }
1551         SetICSMode();
1552         telnetISR =
1553           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1554         fromUserISR =
1555           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1556         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1557             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1558     } else if (appData.noChessProgram) {
1559         SetNCPMode();
1560     } else {
1561         SetGNUMode();
1562     }
1563
1564     if (*appData.cmailGameName != NULLCHAR) {
1565         SetCmailMode();
1566         OpenLoopback(&cmailPR);
1567         cmailISR =
1568           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1569     }
1570
1571     ThawUI();
1572     DisplayMessage("", "");
1573     if (StrCaseCmp(appData.initialMode, "") == 0) {
1574       initialMode = BeginningOfGame;
1575       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1576         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1577         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1578         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1579         ModeHighlight();
1580       }
1581     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1582       initialMode = TwoMachinesPlay;
1583     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1584       initialMode = AnalyzeFile;
1585     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1586       initialMode = AnalyzeMode;
1587     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1588       initialMode = MachinePlaysWhite;
1589     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1590       initialMode = MachinePlaysBlack;
1591     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1592       initialMode = EditGame;
1593     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1594       initialMode = EditPosition;
1595     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1596       initialMode = Training;
1597     } else {
1598       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1599       if( (len >= MSG_SIZ) && appData.debugMode )
1600         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1601
1602       DisplayFatalError(buf, 0, 2);
1603       return;
1604     }
1605
1606     if (appData.matchMode) {
1607         if(appData.tourneyFile[0]) { // start tourney from command line
1608             FILE *f;
1609             if(f = fopen(appData.tourneyFile, "r")) {
1610                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1611                 fclose(f);
1612                 appData.clockMode = TRUE;
1613                 SetGNUMode();
1614             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1615         }
1616         MatchEvent(TRUE);
1617     } else if (*appData.cmailGameName != NULLCHAR) {
1618         /* Set up cmail mode */
1619         ReloadCmailMsgEvent(TRUE);
1620     } else {
1621         /* Set up other modes */
1622         if (initialMode == AnalyzeFile) {
1623           if (*appData.loadGameFile == NULLCHAR) {
1624             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1625             return;
1626           }
1627         }
1628         if (*appData.loadGameFile != NULLCHAR) {
1629             (void) LoadGameFromFile(appData.loadGameFile,
1630                                     appData.loadGameIndex,
1631                                     appData.loadGameFile, TRUE);
1632         } else if (*appData.loadPositionFile != NULLCHAR) {
1633             (void) LoadPositionFromFile(appData.loadPositionFile,
1634                                         appData.loadPositionIndex,
1635                                         appData.loadPositionFile);
1636             /* [HGM] try to make self-starting even after FEN load */
1637             /* to allow automatic setup of fairy variants with wtm */
1638             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1639                 gameMode = BeginningOfGame;
1640                 setboardSpoiledMachineBlack = 1;
1641             }
1642             /* [HGM] loadPos: make that every new game uses the setup */
1643             /* from file as long as we do not switch variant          */
1644             if(!blackPlaysFirst) {
1645                 startedFromPositionFile = TRUE;
1646                 CopyBoard(filePosition, boards[0]);
1647             }
1648         }
1649         if (initialMode == AnalyzeMode) {
1650           if (appData.noChessProgram) {
1651             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1656             return;
1657           }
1658           AnalyzeModeEvent();
1659         } else if (initialMode == AnalyzeFile) {
1660           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1661           ShowThinkingEvent();
1662           AnalyzeFileEvent();
1663           AnalysisPeriodicEvent(1);
1664         } else if (initialMode == MachinePlaysWhite) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           MachineWhiteEvent();
1676         } else if (initialMode == MachinePlaysBlack) {
1677           if (appData.noChessProgram) {
1678             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1679                               0, 2);
1680             return;
1681           }
1682           if (appData.icsActive) {
1683             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1684                               0, 2);
1685             return;
1686           }
1687           MachineBlackEvent();
1688         } else if (initialMode == TwoMachinesPlay) {
1689           if (appData.noChessProgram) {
1690             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1691                               0, 2);
1692             return;
1693           }
1694           if (appData.icsActive) {
1695             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1696                               0, 2);
1697             return;
1698           }
1699           TwoMachinesEvent();
1700         } else if (initialMode == EditGame) {
1701           EditGameEvent();
1702         } else if (initialMode == EditPosition) {
1703           EditPositionEvent();
1704         } else if (initialMode == Training) {
1705           if (*appData.loadGameFile == NULLCHAR) {
1706             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1707             return;
1708           }
1709           TrainingEvent();
1710         }
1711     }
1712 }
1713
1714 void
1715 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1716 {
1717     DisplayBook(current+1);
1718
1719     MoveHistorySet( movelist, first, last, current, pvInfoList );
1720
1721     EvalGraphSet( first, last, current, pvInfoList );
1722
1723     MakeEngineOutputTitle();
1724 }
1725
1726 /*
1727  * Establish will establish a contact to a remote host.port.
1728  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1729  *  used to talk to the host.
1730  * Returns 0 if okay, error code if not.
1731  */
1732 int
1733 establish ()
1734 {
1735     char buf[MSG_SIZ];
1736
1737     if (*appData.icsCommPort != NULLCHAR) {
1738         /* Talk to the host through a serial comm port */
1739         return OpenCommPort(appData.icsCommPort, &icsPR);
1740
1741     } else if (*appData.gateway != NULLCHAR) {
1742         if (*appData.remoteShell == NULLCHAR) {
1743             /* Use the rcmd protocol to run telnet program on a gateway host */
1744             snprintf(buf, sizeof(buf), "%s %s %s",
1745                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1746             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1747
1748         } else {
1749             /* Use the rsh program to run telnet program on a gateway host */
1750             if (*appData.remoteUser == NULLCHAR) {
1751                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1752                         appData.gateway, appData.telnetProgram,
1753                         appData.icsHost, appData.icsPort);
1754             } else {
1755                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1756                         appData.remoteShell, appData.gateway,
1757                         appData.remoteUser, appData.telnetProgram,
1758                         appData.icsHost, appData.icsPort);
1759             }
1760             return StartChildProcess(buf, "", &icsPR);
1761
1762         }
1763     } else if (appData.useTelnet) {
1764         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1765
1766     } else {
1767         /* TCP socket interface differs somewhat between
1768            Unix and NT; handle details in the front end.
1769            */
1770         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1771     }
1772 }
1773
1774 void
1775 EscapeExpand (char *p, char *q)
1776 {       // [HGM] initstring: routine to shape up string arguments
1777         while(*p++ = *q++) if(p[-1] == '\\')
1778             switch(*q++) {
1779                 case 'n': p[-1] = '\n'; break;
1780                 case 'r': p[-1] = '\r'; break;
1781                 case 't': p[-1] = '\t'; break;
1782                 case '\\': p[-1] = '\\'; break;
1783                 case 0: *p = 0; return;
1784                 default: p[-1] = q[-1]; break;
1785             }
1786 }
1787
1788 void
1789 show_bytes (FILE *fp, char *buf, int count)
1790 {
1791     while (count--) {
1792         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1793             fprintf(fp, "\\%03o", *buf & 0xff);
1794         } else {
1795             putc(*buf, fp);
1796         }
1797         buf++;
1798     }
1799     fflush(fp);
1800 }
1801
1802 /* Returns an errno value */
1803 int
1804 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1805 {
1806     char buf[8192], *p, *q, *buflim;
1807     int left, newcount, outcount;
1808
1809     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1810         *appData.gateway != NULLCHAR) {
1811         if (appData.debugMode) {
1812             fprintf(debugFP, ">ICS: ");
1813             show_bytes(debugFP, message, count);
1814             fprintf(debugFP, "\n");
1815         }
1816         return OutputToProcess(pr, message, count, outError);
1817     }
1818
1819     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1820     p = message;
1821     q = buf;
1822     left = count;
1823     newcount = 0;
1824     while (left) {
1825         if (q >= buflim) {
1826             if (appData.debugMode) {
1827                 fprintf(debugFP, ">ICS: ");
1828                 show_bytes(debugFP, buf, newcount);
1829                 fprintf(debugFP, "\n");
1830             }
1831             outcount = OutputToProcess(pr, buf, newcount, outError);
1832             if (outcount < newcount) return -1; /* to be sure */
1833             q = buf;
1834             newcount = 0;
1835         }
1836         if (*p == '\n') {
1837             *q++ = '\r';
1838             newcount++;
1839         } else if (((unsigned char) *p) == TN_IAC) {
1840             *q++ = (char) TN_IAC;
1841             newcount ++;
1842         }
1843         *q++ = *p++;
1844         newcount++;
1845         left--;
1846     }
1847     if (appData.debugMode) {
1848         fprintf(debugFP, ">ICS: ");
1849         show_bytes(debugFP, buf, newcount);
1850         fprintf(debugFP, "\n");
1851     }
1852     outcount = OutputToProcess(pr, buf, newcount, outError);
1853     if (outcount < newcount) return -1; /* to be sure */
1854     return count;
1855 }
1856
1857 void
1858 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1859 {
1860     int outError, outCount;
1861     static int gotEof = 0;
1862     static FILE *ini;
1863
1864     /* Pass data read from player on to ICS */
1865     if (count > 0) {
1866         gotEof = 0;
1867         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1868         if (outCount < count) {
1869             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1870         }
1871         if(have_sent_ICS_logon == 2) {
1872           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1873             fprintf(ini, "%s", message);
1874             have_sent_ICS_logon = 3;
1875           } else
1876             have_sent_ICS_logon = 1;
1877         } else if(have_sent_ICS_logon == 3) {
1878             fprintf(ini, "%s", message);
1879             fclose(ini);
1880           have_sent_ICS_logon = 1;
1881         }
1882     } else if (count < 0) {
1883         RemoveInputSource(isr);
1884         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1885     } else if (gotEof++ > 0) {
1886         RemoveInputSource(isr);
1887         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1888     }
1889 }
1890
1891 void
1892 KeepAlive ()
1893 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1894     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1895     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1896     SendToICS("date\n");
1897     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1898 }
1899
1900 /* added routine for printf style output to ics */
1901 void
1902 ics_printf (char *format, ...)
1903 {
1904     char buffer[MSG_SIZ];
1905     va_list args;
1906
1907     va_start(args, format);
1908     vsnprintf(buffer, sizeof(buffer), format, args);
1909     buffer[sizeof(buffer)-1] = '\0';
1910     SendToICS(buffer);
1911     va_end(args);
1912 }
1913
1914 void
1915 SendToICS (char *s)
1916 {
1917     int count, outCount, outError;
1918
1919     if (icsPR == NoProc) return;
1920
1921     count = strlen(s);
1922     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1923     if (outCount < count) {
1924         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1925     }
1926 }
1927
1928 /* This is used for sending logon scripts to the ICS. Sending
1929    without a delay causes problems when using timestamp on ICC
1930    (at least on my machine). */
1931 void
1932 SendToICSDelayed (char *s, long msdelay)
1933 {
1934     int count, outCount, outError;
1935
1936     if (icsPR == NoProc) return;
1937
1938     count = strlen(s);
1939     if (appData.debugMode) {
1940         fprintf(debugFP, ">ICS: ");
1941         show_bytes(debugFP, s, count);
1942         fprintf(debugFP, "\n");
1943     }
1944     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1945                                       msdelay);
1946     if (outCount < count) {
1947         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1948     }
1949 }
1950
1951
1952 /* Remove all highlighting escape sequences in s
1953    Also deletes any suffix starting with '('
1954    */
1955 char *
1956 StripHighlightAndTitle (char *s)
1957 {
1958     static char retbuf[MSG_SIZ];
1959     char *p = retbuf;
1960
1961     while (*s != NULLCHAR) {
1962         while (*s == '\033') {
1963             while (*s != NULLCHAR && !isalpha(*s)) s++;
1964             if (*s != NULLCHAR) s++;
1965         }
1966         while (*s != NULLCHAR && *s != '\033') {
1967             if (*s == '(' || *s == '[') {
1968                 *p = NULLCHAR;
1969                 return retbuf;
1970             }
1971             *p++ = *s++;
1972         }
1973     }
1974     *p = NULLCHAR;
1975     return retbuf;
1976 }
1977
1978 /* Remove all highlighting escape sequences in s */
1979 char *
1980 StripHighlight (char *s)
1981 {
1982     static char retbuf[MSG_SIZ];
1983     char *p = retbuf;
1984
1985     while (*s != NULLCHAR) {
1986         while (*s == '\033') {
1987             while (*s != NULLCHAR && !isalpha(*s)) s++;
1988             if (*s != NULLCHAR) s++;
1989         }
1990         while (*s != NULLCHAR && *s != '\033') {
1991             *p++ = *s++;
1992         }
1993     }
1994     *p = NULLCHAR;
1995     return retbuf;
1996 }
1997
1998 char *variantNames[] = VARIANT_NAMES;
1999 char *
2000 VariantName (VariantClass v)
2001 {
2002     return variantNames[v];
2003 }
2004
2005
2006 /* Identify a variant from the strings the chess servers use or the
2007    PGN Variant tag names we use. */
2008 VariantClass
2009 StringToVariant (char *e)
2010 {
2011     char *p;
2012     int wnum = -1;
2013     VariantClass v = VariantNormal;
2014     int i, found = FALSE;
2015     char buf[MSG_SIZ];
2016     int len;
2017
2018     if (!e) return v;
2019
2020     /* [HGM] skip over optional board-size prefixes */
2021     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2022         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2023         while( *e++ != '_');
2024     }
2025
2026     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2027         v = VariantNormal;
2028         found = TRUE;
2029     } else
2030     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2031       if (StrCaseStr(e, variantNames[i])) {
2032         v = (VariantClass) i;
2033         found = TRUE;
2034         break;
2035       }
2036     }
2037
2038     if (!found) {
2039       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2040           || StrCaseStr(e, "wild/fr")
2041           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2042         v = VariantFischeRandom;
2043       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2044                  (i = 1, p = StrCaseStr(e, "w"))) {
2045         p += i;
2046         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2047         if (isdigit(*p)) {
2048           wnum = atoi(p);
2049         } else {
2050           wnum = -1;
2051         }
2052         switch (wnum) {
2053         case 0: /* FICS only, actually */
2054         case 1:
2055           /* Castling legal even if K starts on d-file */
2056           v = VariantWildCastle;
2057           break;
2058         case 2:
2059         case 3:
2060         case 4:
2061           /* Castling illegal even if K & R happen to start in
2062              normal positions. */
2063           v = VariantNoCastle;
2064           break;
2065         case 5:
2066         case 7:
2067         case 8:
2068         case 10:
2069         case 11:
2070         case 12:
2071         case 13:
2072         case 14:
2073         case 15:
2074         case 18:
2075         case 19:
2076           /* Castling legal iff K & R start in normal positions */
2077           v = VariantNormal;
2078           break;
2079         case 6:
2080         case 20:
2081         case 21:
2082           /* Special wilds for position setup; unclear what to do here */
2083           v = VariantLoadable;
2084           break;
2085         case 9:
2086           /* Bizarre ICC game */
2087           v = VariantTwoKings;
2088           break;
2089         case 16:
2090           v = VariantKriegspiel;
2091           break;
2092         case 17:
2093           v = VariantLosers;
2094           break;
2095         case 22:
2096           v = VariantFischeRandom;
2097           break;
2098         case 23:
2099           v = VariantCrazyhouse;
2100           break;
2101         case 24:
2102           v = VariantBughouse;
2103           break;
2104         case 25:
2105           v = Variant3Check;
2106           break;
2107         case 26:
2108           /* Not quite the same as FICS suicide! */
2109           v = VariantGiveaway;
2110           break;
2111         case 27:
2112           v = VariantAtomic;
2113           break;
2114         case 28:
2115           v = VariantShatranj;
2116           break;
2117
2118         /* Temporary names for future ICC types.  The name *will* change in
2119            the next xboard/WinBoard release after ICC defines it. */
2120         case 29:
2121           v = Variant29;
2122           break;
2123         case 30:
2124           v = Variant30;
2125           break;
2126         case 31:
2127           v = Variant31;
2128           break;
2129         case 32:
2130           v = Variant32;
2131           break;
2132         case 33:
2133           v = Variant33;
2134           break;
2135         case 34:
2136           v = Variant34;
2137           break;
2138         case 35:
2139           v = Variant35;
2140           break;
2141         case 36:
2142           v = Variant36;
2143           break;
2144         case 37:
2145           v = VariantShogi;
2146           break;
2147         case 38:
2148           v = VariantXiangqi;
2149           break;
2150         case 39:
2151           v = VariantCourier;
2152           break;
2153         case 40:
2154           v = VariantGothic;
2155           break;
2156         case 41:
2157           v = VariantCapablanca;
2158           break;
2159         case 42:
2160           v = VariantKnightmate;
2161           break;
2162         case 43:
2163           v = VariantFairy;
2164           break;
2165         case 44:
2166           v = VariantCylinder;
2167           break;
2168         case 45:
2169           v = VariantFalcon;
2170           break;
2171         case 46:
2172           v = VariantCapaRandom;
2173           break;
2174         case 47:
2175           v = VariantBerolina;
2176           break;
2177         case 48:
2178           v = VariantJanus;
2179           break;
2180         case 49:
2181           v = VariantSuper;
2182           break;
2183         case 50:
2184           v = VariantGreat;
2185           break;
2186         case -1:
2187           /* Found "wild" or "w" in the string but no number;
2188              must assume it's normal chess. */
2189           v = VariantNormal;
2190           break;
2191         default:
2192           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2193           if( (len >= MSG_SIZ) && appData.debugMode )
2194             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2195
2196           DisplayError(buf, 0);
2197           v = VariantUnknown;
2198           break;
2199         }
2200       }
2201     }
2202     if (appData.debugMode) {
2203       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2204               e, wnum, VariantName(v));
2205     }
2206     return v;
2207 }
2208
2209 static int leftover_start = 0, leftover_len = 0;
2210 char star_match[STAR_MATCH_N][MSG_SIZ];
2211
2212 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2213    advance *index beyond it, and set leftover_start to the new value of
2214    *index; else return FALSE.  If pattern contains the character '*', it
2215    matches any sequence of characters not containing '\r', '\n', or the
2216    character following the '*' (if any), and the matched sequence(s) are
2217    copied into star_match.
2218    */
2219 int
2220 looking_at ( char *buf, int *index, char *pattern)
2221 {
2222     char *bufp = &buf[*index], *patternp = pattern;
2223     int star_count = 0;
2224     char *matchp = star_match[0];
2225
2226     for (;;) {
2227         if (*patternp == NULLCHAR) {
2228             *index = leftover_start = bufp - buf;
2229             *matchp = NULLCHAR;
2230             return TRUE;
2231         }
2232         if (*bufp == NULLCHAR) return FALSE;
2233         if (*patternp == '*') {
2234             if (*bufp == *(patternp + 1)) {
2235                 *matchp = NULLCHAR;
2236                 matchp = star_match[++star_count];
2237                 patternp += 2;
2238                 bufp++;
2239                 continue;
2240             } else if (*bufp == '\n' || *bufp == '\r') {
2241                 patternp++;
2242                 if (*patternp == NULLCHAR)
2243                   continue;
2244                 else
2245                   return FALSE;
2246             } else {
2247                 *matchp++ = *bufp++;
2248                 continue;
2249             }
2250         }
2251         if (*patternp != *bufp) return FALSE;
2252         patternp++;
2253         bufp++;
2254     }
2255 }
2256
2257 void
2258 SendToPlayer (char *data, int length)
2259 {
2260     int error, outCount;
2261     outCount = OutputToProcess(NoProc, data, length, &error);
2262     if (outCount < length) {
2263         DisplayFatalError(_("Error writing to display"), error, 1);
2264     }
2265 }
2266
2267 void
2268 PackHolding (char packed[], char *holding)
2269 {
2270     char *p = holding;
2271     char *q = packed;
2272     int runlength = 0;
2273     int curr = 9999;
2274     do {
2275         if (*p == curr) {
2276             runlength++;
2277         } else {
2278             switch (runlength) {
2279               case 0:
2280                 break;
2281               case 1:
2282                 *q++ = curr;
2283                 break;
2284               case 2:
2285                 *q++ = curr;
2286                 *q++ = curr;
2287                 break;
2288               default:
2289                 sprintf(q, "%d", runlength);
2290                 while (*q) q++;
2291                 *q++ = curr;
2292                 break;
2293             }
2294             runlength = 1;
2295             curr = *p;
2296         }
2297     } while (*p++);
2298     *q = NULLCHAR;
2299 }
2300
2301 /* Telnet protocol requests from the front end */
2302 void
2303 TelnetRequest (unsigned char ddww, unsigned char option)
2304 {
2305     unsigned char msg[3];
2306     int outCount, outError;
2307
2308     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2309
2310     if (appData.debugMode) {
2311         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2312         switch (ddww) {
2313           case TN_DO:
2314             ddwwStr = "DO";
2315             break;
2316           case TN_DONT:
2317             ddwwStr = "DONT";
2318             break;
2319           case TN_WILL:
2320             ddwwStr = "WILL";
2321             break;
2322           case TN_WONT:
2323             ddwwStr = "WONT";
2324             break;
2325           default:
2326             ddwwStr = buf1;
2327             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2328             break;
2329         }
2330         switch (option) {
2331           case TN_ECHO:
2332             optionStr = "ECHO";
2333             break;
2334           default:
2335             optionStr = buf2;
2336             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2337             break;
2338         }
2339         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2340     }
2341     msg[0] = TN_IAC;
2342     msg[1] = ddww;
2343     msg[2] = option;
2344     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2345     if (outCount < 3) {
2346         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2347     }
2348 }
2349
2350 void
2351 DoEcho ()
2352 {
2353     if (!appData.icsActive) return;
2354     TelnetRequest(TN_DO, TN_ECHO);
2355 }
2356
2357 void
2358 DontEcho ()
2359 {
2360     if (!appData.icsActive) return;
2361     TelnetRequest(TN_DONT, TN_ECHO);
2362 }
2363
2364 void
2365 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2366 {
2367     /* put the holdings sent to us by the server on the board holdings area */
2368     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2369     char p;
2370     ChessSquare piece;
2371
2372     if(gameInfo.holdingsWidth < 2)  return;
2373     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2374         return; // prevent overwriting by pre-board holdings
2375
2376     if( (int)lowestPiece >= BlackPawn ) {
2377         holdingsColumn = 0;
2378         countsColumn = 1;
2379         holdingsStartRow = BOARD_HEIGHT-1;
2380         direction = -1;
2381     } else {
2382         holdingsColumn = BOARD_WIDTH-1;
2383         countsColumn = BOARD_WIDTH-2;
2384         holdingsStartRow = 0;
2385         direction = 1;
2386     }
2387
2388     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2389         board[i][holdingsColumn] = EmptySquare;
2390         board[i][countsColumn]   = (ChessSquare) 0;
2391     }
2392     while( (p=*holdings++) != NULLCHAR ) {
2393         piece = CharToPiece( ToUpper(p) );
2394         if(piece == EmptySquare) continue;
2395         /*j = (int) piece - (int) WhitePawn;*/
2396         j = PieceToNumber(piece);
2397         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2398         if(j < 0) continue;               /* should not happen */
2399         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2400         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2401         board[holdingsStartRow+j*direction][countsColumn]++;
2402     }
2403 }
2404
2405
2406 void
2407 VariantSwitch (Board board, VariantClass newVariant)
2408 {
2409    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2410    static Board oldBoard;
2411
2412    startedFromPositionFile = FALSE;
2413    if(gameInfo.variant == newVariant) return;
2414
2415    /* [HGM] This routine is called each time an assignment is made to
2416     * gameInfo.variant during a game, to make sure the board sizes
2417     * are set to match the new variant. If that means adding or deleting
2418     * holdings, we shift the playing board accordingly
2419     * This kludge is needed because in ICS observe mode, we get boards
2420     * of an ongoing game without knowing the variant, and learn about the
2421     * latter only later. This can be because of the move list we requested,
2422     * in which case the game history is refilled from the beginning anyway,
2423     * but also when receiving holdings of a crazyhouse game. In the latter
2424     * case we want to add those holdings to the already received position.
2425     */
2426
2427
2428    if (appData.debugMode) {
2429      fprintf(debugFP, "Switch board from %s to %s\n",
2430              VariantName(gameInfo.variant), VariantName(newVariant));
2431      setbuf(debugFP, NULL);
2432    }
2433    shuffleOpenings = 0;       /* [HGM] shuffle */
2434    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2435    switch(newVariant)
2436      {
2437      case VariantShogi:
2438        newWidth = 9;  newHeight = 9;
2439        gameInfo.holdingsSize = 7;
2440      case VariantBughouse:
2441      case VariantCrazyhouse:
2442        newHoldingsWidth = 2; break;
2443      case VariantGreat:
2444        newWidth = 10;
2445      case VariantSuper:
2446        newHoldingsWidth = 2;
2447        gameInfo.holdingsSize = 8;
2448        break;
2449      case VariantGothic:
2450      case VariantCapablanca:
2451      case VariantCapaRandom:
2452        newWidth = 10;
2453      default:
2454        newHoldingsWidth = gameInfo.holdingsSize = 0;
2455      };
2456
2457    if(newWidth  != gameInfo.boardWidth  ||
2458       newHeight != gameInfo.boardHeight ||
2459       newHoldingsWidth != gameInfo.holdingsWidth ) {
2460
2461      /* shift position to new playing area, if needed */
2462      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2463        for(i=0; i<BOARD_HEIGHT; i++)
2464          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2465            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2466              board[i][j];
2467        for(i=0; i<newHeight; i++) {
2468          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2469          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2470        }
2471      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2472        for(i=0; i<BOARD_HEIGHT; i++)
2473          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2474            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2475              board[i][j];
2476      }
2477      board[HOLDINGS_SET] = 0;
2478      gameInfo.boardWidth  = newWidth;
2479      gameInfo.boardHeight = newHeight;
2480      gameInfo.holdingsWidth = newHoldingsWidth;
2481      gameInfo.variant = newVariant;
2482      InitDrawingSizes(-2, 0);
2483    } else gameInfo.variant = newVariant;
2484    CopyBoard(oldBoard, board);   // remember correctly formatted board
2485      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2486    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2487 }
2488
2489 static int loggedOn = FALSE;
2490
2491 /*-- Game start info cache: --*/
2492 int gs_gamenum;
2493 char gs_kind[MSG_SIZ];
2494 static char player1Name[128] = "";
2495 static char player2Name[128] = "";
2496 static char cont_seq[] = "\n\\   ";
2497 static int player1Rating = -1;
2498 static int player2Rating = -1;
2499 /*----------------------------*/
2500
2501 ColorClass curColor = ColorNormal;
2502 int suppressKibitz = 0;
2503
2504 // [HGM] seekgraph
2505 Boolean soughtPending = FALSE;
2506 Boolean seekGraphUp;
2507 #define MAX_SEEK_ADS 200
2508 #define SQUARE 0x80
2509 char *seekAdList[MAX_SEEK_ADS];
2510 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2511 float tcList[MAX_SEEK_ADS];
2512 char colorList[MAX_SEEK_ADS];
2513 int nrOfSeekAds = 0;
2514 int minRating = 1010, maxRating = 2800;
2515 int hMargin = 10, vMargin = 20, h, w;
2516 extern int squareSize, lineGap;
2517
2518 void
2519 PlotSeekAd (int i)
2520 {
2521         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2522         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2523         if(r < minRating+100 && r >=0 ) r = minRating+100;
2524         if(r > maxRating) r = maxRating;
2525         if(tc < 1.f) tc = 1.f;
2526         if(tc > 95.f) tc = 95.f;
2527         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2528         y = ((double)r - minRating)/(maxRating - minRating)
2529             * (h-vMargin-squareSize/8-1) + vMargin;
2530         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2531         if(strstr(seekAdList[i], " u ")) color = 1;
2532         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2533            !strstr(seekAdList[i], "bullet") &&
2534            !strstr(seekAdList[i], "blitz") &&
2535            !strstr(seekAdList[i], "standard") ) color = 2;
2536         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2537         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2538 }
2539
2540 void
2541 PlotSingleSeekAd (int i)
2542 {
2543         PlotSeekAd(i);
2544 }
2545
2546 void
2547 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2548 {
2549         char buf[MSG_SIZ], *ext = "";
2550         VariantClass v = StringToVariant(type);
2551         if(strstr(type, "wild")) {
2552             ext = type + 4; // append wild number
2553             if(v == VariantFischeRandom) type = "chess960"; else
2554             if(v == VariantLoadable) type = "setup"; else
2555             type = VariantName(v);
2556         }
2557         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2558         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2559             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2560             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2561             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2562             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2563             seekNrList[nrOfSeekAds] = nr;
2564             zList[nrOfSeekAds] = 0;
2565             seekAdList[nrOfSeekAds++] = StrSave(buf);
2566             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2567         }
2568 }
2569
2570 void
2571 EraseSeekDot (int i)
2572 {
2573     int x = xList[i], y = yList[i], d=squareSize/4, k;
2574     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2575     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2576     // now replot every dot that overlapped
2577     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2578         int xx = xList[k], yy = yList[k];
2579         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2580             DrawSeekDot(xx, yy, colorList[k]);
2581     }
2582 }
2583
2584 void
2585 RemoveSeekAd (int nr)
2586 {
2587         int i;
2588         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2589             EraseSeekDot(i);
2590             if(seekAdList[i]) free(seekAdList[i]);
2591             seekAdList[i] = seekAdList[--nrOfSeekAds];
2592             seekNrList[i] = seekNrList[nrOfSeekAds];
2593             ratingList[i] = ratingList[nrOfSeekAds];
2594             colorList[i]  = colorList[nrOfSeekAds];
2595             tcList[i] = tcList[nrOfSeekAds];
2596             xList[i]  = xList[nrOfSeekAds];
2597             yList[i]  = yList[nrOfSeekAds];
2598             zList[i]  = zList[nrOfSeekAds];
2599             seekAdList[nrOfSeekAds] = NULL;
2600             break;
2601         }
2602 }
2603
2604 Boolean
2605 MatchSoughtLine (char *line)
2606 {
2607     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2608     int nr, base, inc, u=0; char dummy;
2609
2610     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2611        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2612        (u=1) &&
2613        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2614         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2615         // match: compact and save the line
2616         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2617         return TRUE;
2618     }
2619     return FALSE;
2620 }
2621
2622 int
2623 DrawSeekGraph ()
2624 {
2625     int i;
2626     if(!seekGraphUp) return FALSE;
2627     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2628     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2629
2630     DrawSeekBackground(0, 0, w, h);
2631     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2632     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2633     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2634         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2635         yy = h-1-yy;
2636         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2637         if(i%500 == 0) {
2638             char buf[MSG_SIZ];
2639             snprintf(buf, MSG_SIZ, "%d", i);
2640             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2641         }
2642     }
2643     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2644     for(i=1; i<100; i+=(i<10?1:5)) {
2645         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2646         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2647         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2648             char buf[MSG_SIZ];
2649             snprintf(buf, MSG_SIZ, "%d", i);
2650             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2651         }
2652     }
2653     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2654     return TRUE;
2655 }
2656
2657 int
2658 SeekGraphClick (ClickType click, int x, int y, int moving)
2659 {
2660     static int lastDown = 0, displayed = 0, lastSecond;
2661     if(y < 0) return FALSE;
2662     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2663         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2664         if(!seekGraphUp) return FALSE;
2665         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2666         DrawPosition(TRUE, NULL);
2667         return TRUE;
2668     }
2669     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2670         if(click == Release || moving) return FALSE;
2671         nrOfSeekAds = 0;
2672         soughtPending = TRUE;
2673         SendToICS(ics_prefix);
2674         SendToICS("sought\n"); // should this be "sought all"?
2675     } else { // issue challenge based on clicked ad
2676         int dist = 10000; int i, closest = 0, second = 0;
2677         for(i=0; i<nrOfSeekAds; i++) {
2678             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2679             if(d < dist) { dist = d; closest = i; }
2680             second += (d - zList[i] < 120); // count in-range ads
2681             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2682         }
2683         if(dist < 120) {
2684             char buf[MSG_SIZ];
2685             second = (second > 1);
2686             if(displayed != closest || second != lastSecond) {
2687                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2688                 lastSecond = second; displayed = closest;
2689             }
2690             if(click == Press) {
2691                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2692                 lastDown = closest;
2693                 return TRUE;
2694             } // on press 'hit', only show info
2695             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2696             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2697             SendToICS(ics_prefix);
2698             SendToICS(buf);
2699             return TRUE; // let incoming board of started game pop down the graph
2700         } else if(click == Release) { // release 'miss' is ignored
2701             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2702             if(moving == 2) { // right up-click
2703                 nrOfSeekAds = 0; // refresh graph
2704                 soughtPending = TRUE;
2705                 SendToICS(ics_prefix);
2706                 SendToICS("sought\n"); // should this be "sought all"?
2707             }
2708             return TRUE;
2709         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2710         // press miss or release hit 'pop down' seek graph
2711         seekGraphUp = FALSE;
2712         DrawPosition(TRUE, NULL);
2713     }
2714     return TRUE;
2715 }
2716
2717 void
2718 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2719 {
2720 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2721 #define STARTED_NONE 0
2722 #define STARTED_MOVES 1
2723 #define STARTED_BOARD 2
2724 #define STARTED_OBSERVE 3
2725 #define STARTED_HOLDINGS 4
2726 #define STARTED_CHATTER 5
2727 #define STARTED_COMMENT 6
2728 #define STARTED_MOVES_NOHIDE 7
2729
2730     static int started = STARTED_NONE;
2731     static char parse[20000];
2732     static int parse_pos = 0;
2733     static char buf[BUF_SIZE + 1];
2734     static int firstTime = TRUE, intfSet = FALSE;
2735     static ColorClass prevColor = ColorNormal;
2736     static int savingComment = FALSE;
2737     static int cmatch = 0; // continuation sequence match
2738     char *bp;
2739     char str[MSG_SIZ];
2740     int i, oldi;
2741     int buf_len;
2742     int next_out;
2743     int tkind;
2744     int backup;    /* [DM] For zippy color lines */
2745     char *p;
2746     char talker[MSG_SIZ]; // [HGM] chat
2747     int channel;
2748
2749     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2750
2751     if (appData.debugMode) {
2752       if (!error) {
2753         fprintf(debugFP, "<ICS: ");
2754         show_bytes(debugFP, data, count);
2755         fprintf(debugFP, "\n");
2756       }
2757     }
2758
2759     if (appData.debugMode) { int f = forwardMostMove;
2760         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2761                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2762                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2763     }
2764     if (count > 0) {
2765         /* If last read ended with a partial line that we couldn't parse,
2766            prepend it to the new read and try again. */
2767         if (leftover_len > 0) {
2768             for (i=0; i<leftover_len; i++)
2769               buf[i] = buf[leftover_start + i];
2770         }
2771
2772     /* copy new characters into the buffer */
2773     bp = buf + leftover_len;
2774     buf_len=leftover_len;
2775     for (i=0; i<count; i++)
2776     {
2777         // ignore these
2778         if (data[i] == '\r')
2779             continue;
2780
2781         // join lines split by ICS?
2782         if (!appData.noJoin)
2783         {
2784             /*
2785                 Joining just consists of finding matches against the
2786                 continuation sequence, and discarding that sequence
2787                 if found instead of copying it.  So, until a match
2788                 fails, there's nothing to do since it might be the
2789                 complete sequence, and thus, something we don't want
2790                 copied.
2791             */
2792             if (data[i] == cont_seq[cmatch])
2793             {
2794                 cmatch++;
2795                 if (cmatch == strlen(cont_seq))
2796                 {
2797                     cmatch = 0; // complete match.  just reset the counter
2798
2799                     /*
2800                         it's possible for the ICS to not include the space
2801                         at the end of the last word, making our [correct]
2802                         join operation fuse two separate words.  the server
2803                         does this when the space occurs at the width setting.
2804                     */
2805                     if (!buf_len || buf[buf_len-1] != ' ')
2806                     {
2807                         *bp++ = ' ';
2808                         buf_len++;
2809                     }
2810                 }
2811                 continue;
2812             }
2813             else if (cmatch)
2814             {
2815                 /*
2816                     match failed, so we have to copy what matched before
2817                     falling through and copying this character.  In reality,
2818                     this will only ever be just the newline character, but
2819                     it doesn't hurt to be precise.
2820                 */
2821                 strncpy(bp, cont_seq, cmatch);
2822                 bp += cmatch;
2823                 buf_len += cmatch;
2824                 cmatch = 0;
2825             }
2826         }
2827
2828         // copy this char
2829         *bp++ = data[i];
2830         buf_len++;
2831     }
2832
2833         buf[buf_len] = NULLCHAR;
2834 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2835         next_out = 0;
2836         leftover_start = 0;
2837
2838         i = 0;
2839         while (i < buf_len) {
2840             /* Deal with part of the TELNET option negotiation
2841                protocol.  We refuse to do anything beyond the
2842                defaults, except that we allow the WILL ECHO option,
2843                which ICS uses to turn off password echoing when we are
2844                directly connected to it.  We reject this option
2845                if localLineEditing mode is on (always on in xboard)
2846                and we are talking to port 23, which might be a real
2847                telnet server that will try to keep WILL ECHO on permanently.
2848              */
2849             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2850                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2851                 unsigned char option;
2852                 oldi = i;
2853                 switch ((unsigned char) buf[++i]) {
2854                   case TN_WILL:
2855                     if (appData.debugMode)
2856                       fprintf(debugFP, "\n<WILL ");
2857                     switch (option = (unsigned char) buf[++i]) {
2858                       case TN_ECHO:
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "ECHO ");
2861                         /* Reply only if this is a change, according
2862                            to the protocol rules. */
2863                         if (remoteEchoOption) break;
2864                         if (appData.localLineEditing &&
2865                             atoi(appData.icsPort) == TN_PORT) {
2866                             TelnetRequest(TN_DONT, TN_ECHO);
2867                         } else {
2868                             EchoOff();
2869                             TelnetRequest(TN_DO, TN_ECHO);
2870                             remoteEchoOption = TRUE;
2871                         }
2872                         break;
2873                       default:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "%d ", option);
2876                         /* Whatever this is, we don't want it. */
2877                         TelnetRequest(TN_DONT, option);
2878                         break;
2879                     }
2880                     break;
2881                   case TN_WONT:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<WONT ");
2884                     switch (option = (unsigned char) buf[++i]) {
2885                       case TN_ECHO:
2886                         if (appData.debugMode)
2887                           fprintf(debugFP, "ECHO ");
2888                         /* Reply only if this is a change, according
2889                            to the protocol rules. */
2890                         if (!remoteEchoOption) break;
2891                         EchoOn();
2892                         TelnetRequest(TN_DONT, TN_ECHO);
2893                         remoteEchoOption = FALSE;
2894                         break;
2895                       default:
2896                         if (appData.debugMode)
2897                           fprintf(debugFP, "%d ", (unsigned char) option);
2898                         /* Whatever this is, it must already be turned
2899                            off, because we never agree to turn on
2900                            anything non-default, so according to the
2901                            protocol rules, we don't reply. */
2902                         break;
2903                     }
2904                     break;
2905                   case TN_DO:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<DO ");
2908                     switch (option = (unsigned char) buf[++i]) {
2909                       default:
2910                         /* Whatever this is, we refuse to do it. */
2911                         if (appData.debugMode)
2912                           fprintf(debugFP, "%d ", option);
2913                         TelnetRequest(TN_WONT, option);
2914                         break;
2915                     }
2916                     break;
2917                   case TN_DONT:
2918                     if (appData.debugMode)
2919                       fprintf(debugFP, "\n<DONT ");
2920                     switch (option = (unsigned char) buf[++i]) {
2921                       default:
2922                         if (appData.debugMode)
2923                           fprintf(debugFP, "%d ", option);
2924                         /* Whatever this is, we are already not doing
2925                            it, because we never agree to do anything
2926                            non-default, so according to the protocol
2927                            rules, we don't reply. */
2928                         break;
2929                     }
2930                     break;
2931                   case TN_IAC:
2932                     if (appData.debugMode)
2933                       fprintf(debugFP, "\n<IAC ");
2934                     /* Doubled IAC; pass it through */
2935                     i--;
2936                     break;
2937                   default:
2938                     if (appData.debugMode)
2939                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2940                     /* Drop all other telnet commands on the floor */
2941                     break;
2942                 }
2943                 if (oldi > next_out)
2944                   SendToPlayer(&buf[next_out], oldi - next_out);
2945                 if (++i > next_out)
2946                   next_out = i;
2947                 continue;
2948             }
2949
2950             /* OK, this at least will *usually* work */
2951             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2952                 loggedOn = TRUE;
2953             }
2954
2955             if (loggedOn && !intfSet) {
2956                 if (ics_type == ICS_ICC) {
2957                   snprintf(str, MSG_SIZ,
2958                           "/set-quietly interface %s\n/set-quietly style 12\n",
2959                           programVersion);
2960                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2961                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2962                 } else if (ics_type == ICS_CHESSNET) {
2963                   snprintf(str, MSG_SIZ, "/style 12\n");
2964                 } else {
2965                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2966                   strcat(str, programVersion);
2967                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2968                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2969                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2970 #ifdef WIN32
2971                   strcat(str, "$iset nohighlight 1\n");
2972 #endif
2973                   strcat(str, "$iset lock 1\n$style 12\n");
2974                 }
2975                 SendToICS(str);
2976                 NotifyFrontendLogin();
2977                 intfSet = TRUE;
2978             }
2979
2980             if (started == STARTED_COMMENT) {
2981                 /* Accumulate characters in comment */
2982                 parse[parse_pos++] = buf[i];
2983                 if (buf[i] == '\n') {
2984                     parse[parse_pos] = NULLCHAR;
2985                     if(chattingPartner>=0) {
2986                         char mess[MSG_SIZ];
2987                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2988                         OutputChatMessage(chattingPartner, mess);
2989                         chattingPartner = -1;
2990                         next_out = i+1; // [HGM] suppress printing in ICS window
2991                     } else
2992                     if(!suppressKibitz) // [HGM] kibitz
2993                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2994                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2995                         int nrDigit = 0, nrAlph = 0, j;
2996                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2997                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2998                         parse[parse_pos] = NULLCHAR;
2999                         // try to be smart: if it does not look like search info, it should go to
3000                         // ICS interaction window after all, not to engine-output window.
3001                         for(j=0; j<parse_pos; j++) { // count letters and digits
3002                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3003                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3004                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3005                         }
3006                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3007                             int depth=0; float score;
3008                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3009                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3010                                 pvInfoList[forwardMostMove-1].depth = depth;
3011                                 pvInfoList[forwardMostMove-1].score = 100*score;
3012                             }
3013                             OutputKibitz(suppressKibitz, parse);
3014                         } else {
3015                             char tmp[MSG_SIZ];
3016                             if(gameMode == IcsObserving) // restore original ICS messages
3017                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3018                             else
3019                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3020                             SendToPlayer(tmp, strlen(tmp));
3021                         }
3022                         next_out = i+1; // [HGM] suppress printing in ICS window
3023                     }
3024                     started = STARTED_NONE;
3025                 } else {
3026                     /* Don't match patterns against characters in comment */
3027                     i++;
3028                     continue;
3029                 }
3030             }
3031             if (started == STARTED_CHATTER) {
3032                 if (buf[i] != '\n') {
3033                     /* Don't match patterns against characters in chatter */
3034                     i++;
3035                     continue;
3036                 }
3037                 started = STARTED_NONE;
3038                 if(suppressKibitz) next_out = i+1;
3039             }
3040
3041             /* Kludge to deal with rcmd protocol */
3042             if (firstTime && looking_at(buf, &i, "\001*")) {
3043                 DisplayFatalError(&buf[1], 0, 1);
3044                 continue;
3045             } else {
3046                 firstTime = FALSE;
3047             }
3048
3049             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3050                 ics_type = ICS_ICC;
3051                 ics_prefix = "/";
3052                 if (appData.debugMode)
3053                   fprintf(debugFP, "ics_type %d\n", ics_type);
3054                 continue;
3055             }
3056             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3057                 ics_type = ICS_FICS;
3058                 ics_prefix = "$";
3059                 if (appData.debugMode)
3060                   fprintf(debugFP, "ics_type %d\n", ics_type);
3061                 continue;
3062             }
3063             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3064                 ics_type = ICS_CHESSNET;
3065                 ics_prefix = "/";
3066                 if (appData.debugMode)
3067                   fprintf(debugFP, "ics_type %d\n", ics_type);
3068                 continue;
3069             }
3070
3071             if (!loggedOn &&
3072                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3073                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3074                  looking_at(buf, &i, "will be \"*\""))) {
3075               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3076               continue;
3077             }
3078
3079             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3080               char buf[MSG_SIZ];
3081               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3082               DisplayIcsInteractionTitle(buf);
3083               have_set_title = TRUE;
3084             }
3085
3086             /* skip finger notes */
3087             if (started == STARTED_NONE &&
3088                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3089                  (buf[i] == '1' && buf[i+1] == '0')) &&
3090                 buf[i+2] == ':' && buf[i+3] == ' ') {
3091               started = STARTED_CHATTER;
3092               i += 3;
3093               continue;
3094             }
3095
3096             oldi = i;
3097             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3098             if(appData.seekGraph) {
3099                 if(soughtPending && MatchSoughtLine(buf+i)) {
3100                     i = strstr(buf+i, "rated") - buf;
3101                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3102                     next_out = leftover_start = i;
3103                     started = STARTED_CHATTER;
3104                     suppressKibitz = TRUE;
3105                     continue;
3106                 }
3107                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3108                         && looking_at(buf, &i, "* ads displayed")) {
3109                     soughtPending = FALSE;
3110                     seekGraphUp = TRUE;
3111                     DrawSeekGraph();
3112                     continue;
3113                 }
3114                 if(appData.autoRefresh) {
3115                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3116                         int s = (ics_type == ICS_ICC); // ICC format differs
3117                         if(seekGraphUp)
3118                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3119                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3120                         looking_at(buf, &i, "*% "); // eat prompt
3121                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i; // suppress
3124                         continue;
3125                     }
3126                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3127                         char *p = star_match[0];
3128                         while(*p) {
3129                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3130                             while(*p && *p++ != ' '); // next
3131                         }
3132                         looking_at(buf, &i, "*% "); // eat prompt
3133                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3134                         next_out = i;
3135                         continue;
3136                     }
3137                 }
3138             }
3139
3140             /* skip formula vars */
3141             if (started == STARTED_NONE &&
3142                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3143               started = STARTED_CHATTER;
3144               i += 3;
3145               continue;
3146             }
3147
3148             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3149             if (appData.autoKibitz && started == STARTED_NONE &&
3150                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3151                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3152                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3153                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3154                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3155                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3156                         suppressKibitz = TRUE;
3157                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3158                         next_out = i;
3159                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3160                                 && (gameMode == IcsPlayingWhite)) ||
3161                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3162                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3163                             started = STARTED_CHATTER; // own kibitz we simply discard
3164                         else {
3165                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3166                             parse_pos = 0; parse[0] = NULLCHAR;
3167                             savingComment = TRUE;
3168                             suppressKibitz = gameMode != IcsObserving ? 2 :
3169                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3170                         }
3171                         continue;
3172                 } else
3173                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3174                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3175                          && atoi(star_match[0])) {
3176                     // suppress the acknowledgements of our own autoKibitz
3177                     char *p;
3178                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3179                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3180                     SendToPlayer(star_match[0], strlen(star_match[0]));
3181                     if(looking_at(buf, &i, "*% ")) // eat prompt
3182                         suppressKibitz = FALSE;
3183                     next_out = i;
3184                     continue;
3185                 }
3186             } // [HGM] kibitz: end of patch
3187
3188             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3189
3190             // [HGM] chat: intercept tells by users for which we have an open chat window
3191             channel = -1;
3192             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3193                                            looking_at(buf, &i, "* whispers:") ||
3194                                            looking_at(buf, &i, "* kibitzes:") ||
3195                                            looking_at(buf, &i, "* shouts:") ||
3196                                            looking_at(buf, &i, "* c-shouts:") ||
3197                                            looking_at(buf, &i, "--> * ") ||
3198                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3199                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3200                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3201                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3202                 int p;
3203                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3204                 chattingPartner = -1;
3205
3206                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3207                 for(p=0; p<MAX_CHAT; p++) {
3208                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3209                     talker[0] = '['; strcat(talker, "] ");
3210                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3211                     chattingPartner = p; break;
3212                     }
3213                 } else
3214                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3215                 for(p=0; p<MAX_CHAT; p++) {
3216                     if(!strcmp("kibitzes", chatPartner[p])) {
3217                         talker[0] = '['; strcat(talker, "] ");
3218                         chattingPartner = p; break;
3219                     }
3220                 } else
3221                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3222                 for(p=0; p<MAX_CHAT; p++) {
3223                     if(!strcmp("whispers", chatPartner[p])) {
3224                         talker[0] = '['; strcat(talker, "] ");
3225                         chattingPartner = p; break;
3226                     }
3227                 } else
3228                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3229                   if(buf[i-8] == '-' && buf[i-3] == 't')
3230                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3231                     if(!strcmp("c-shouts", chatPartner[p])) {
3232                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3233                         chattingPartner = p; break;
3234                     }
3235                   }
3236                   if(chattingPartner < 0)
3237                   for(p=0; p<MAX_CHAT; p++) {
3238                     if(!strcmp("shouts", chatPartner[p])) {
3239                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3240                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3241                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3242                         chattingPartner = p; break;
3243                     }
3244                   }
3245                 }
3246                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3247                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3248                     talker[0] = 0; Colorize(ColorTell, FALSE);
3249                     chattingPartner = p; break;
3250                 }
3251                 if(chattingPartner<0) i = oldi; else {
3252                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3253                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3254                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3255                     started = STARTED_COMMENT;
3256                     parse_pos = 0; parse[0] = NULLCHAR;
3257                     savingComment = 3 + chattingPartner; // counts as TRUE
3258                     suppressKibitz = TRUE;
3259                     continue;
3260                 }
3261             } // [HGM] chat: end of patch
3262
3263           backup = i;
3264             if (appData.zippyTalk || appData.zippyPlay) {
3265                 /* [DM] Backup address for color zippy lines */
3266 #if ZIPPY
3267                if (loggedOn == TRUE)
3268                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3269                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3270 #endif
3271             } // [DM] 'else { ' deleted
3272                 if (
3273                     /* Regular tells and says */
3274                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3275                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3276                     looking_at(buf, &i, "* says: ") ||
3277                     /* Don't color "message" or "messages" output */
3278                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3279                     looking_at(buf, &i, "*. * at *:*: ") ||
3280                     looking_at(buf, &i, "--* (*:*): ") ||
3281                     /* Message notifications (same color as tells) */
3282                     looking_at(buf, &i, "* has left a message ") ||
3283                     looking_at(buf, &i, "* just sent you a message:\n") ||
3284                     /* Whispers and kibitzes */
3285                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3286                     looking_at(buf, &i, "* kibitzes: ") ||
3287                     /* Channel tells */
3288                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3289
3290                   if (tkind == 1 && strchr(star_match[0], ':')) {
3291                       /* Avoid "tells you:" spoofs in channels */
3292                      tkind = 3;
3293                   }
3294                   if (star_match[0][0] == NULLCHAR ||
3295                       strchr(star_match[0], ' ') ||
3296                       (tkind == 3 && strchr(star_match[1], ' '))) {
3297                     /* Reject bogus matches */
3298                     i = oldi;
3299                   } else {
3300                     if (appData.colorize) {
3301                       if (oldi > next_out) {
3302                         SendToPlayer(&buf[next_out], oldi - next_out);
3303                         next_out = oldi;
3304                       }
3305                       switch (tkind) {
3306                       case 1:
3307                         Colorize(ColorTell, FALSE);
3308                         curColor = ColorTell;
3309                         break;
3310                       case 2:
3311                         Colorize(ColorKibitz, FALSE);
3312                         curColor = ColorKibitz;
3313                         break;
3314                       case 3:
3315                         p = strrchr(star_match[1], '(');
3316                         if (p == NULL) {
3317                           p = star_match[1];
3318                         } else {
3319                           p++;
3320                         }
3321                         if (atoi(p) == 1) {
3322                           Colorize(ColorChannel1, FALSE);
3323                           curColor = ColorChannel1;
3324                         } else {
3325                           Colorize(ColorChannel, FALSE);
3326                           curColor = ColorChannel;
3327                         }
3328                         break;
3329                       case 5:
3330                         curColor = ColorNormal;
3331                         break;
3332                       }
3333                     }
3334                     if (started == STARTED_NONE && appData.autoComment &&
3335                         (gameMode == IcsObserving ||
3336                          gameMode == IcsPlayingWhite ||
3337                          gameMode == IcsPlayingBlack)) {
3338                       parse_pos = i - oldi;
3339                       memcpy(parse, &buf[oldi], parse_pos);
3340                       parse[parse_pos] = NULLCHAR;
3341                       started = STARTED_COMMENT;
3342                       savingComment = TRUE;
3343                     } else {
3344                       started = STARTED_CHATTER;
3345                       savingComment = FALSE;
3346                     }
3347                     loggedOn = TRUE;
3348                     continue;
3349                   }
3350                 }
3351
3352                 if (looking_at(buf, &i, "* s-shouts: ") ||
3353                     looking_at(buf, &i, "* c-shouts: ")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorSShout, FALSE);
3360                         curColor = ColorSShout;
3361                     }
3362                     loggedOn = TRUE;
3363                     started = STARTED_CHATTER;
3364                     continue;
3365                 }
3366
3367                 if (looking_at(buf, &i, "--->")) {
3368                     loggedOn = TRUE;
3369                     continue;
3370                 }
3371
3372                 if (looking_at(buf, &i, "* shouts: ") ||
3373                     looking_at(buf, &i, "--> ")) {
3374                     if (appData.colorize) {
3375                         if (oldi > next_out) {
3376                             SendToPlayer(&buf[next_out], oldi - next_out);
3377                             next_out = oldi;
3378                         }
3379                         Colorize(ColorShout, FALSE);
3380                         curColor = ColorShout;
3381                     }
3382                     loggedOn = TRUE;
3383                     started = STARTED_CHATTER;
3384                     continue;
3385                 }
3386
3387                 if (looking_at( buf, &i, "Challenge:")) {
3388                     if (appData.colorize) {
3389                         if (oldi > next_out) {
3390                             SendToPlayer(&buf[next_out], oldi - next_out);
3391                             next_out = oldi;
3392                         }
3393                         Colorize(ColorChallenge, FALSE);
3394                         curColor = ColorChallenge;
3395                     }
3396                     loggedOn = TRUE;
3397                     continue;
3398                 }
3399
3400                 if (looking_at(buf, &i, "* offers you") ||
3401                     looking_at(buf, &i, "* offers to be") ||
3402                     looking_at(buf, &i, "* would like to") ||
3403                     looking_at(buf, &i, "* requests to") ||
3404                     looking_at(buf, &i, "Your opponent offers") ||
3405                     looking_at(buf, &i, "Your opponent requests")) {
3406
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorRequest, FALSE);
3413                         curColor = ColorRequest;
3414                     }
3415                     continue;
3416                 }
3417
3418                 if (looking_at(buf, &i, "* (*) seeking")) {
3419                     if (appData.colorize) {
3420                         if (oldi > next_out) {
3421                             SendToPlayer(&buf[next_out], oldi - next_out);
3422                             next_out = oldi;
3423                         }
3424                         Colorize(ColorSeek, FALSE);
3425                         curColor = ColorSeek;
3426                     }
3427                     continue;
3428             }
3429
3430           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3431
3432             if (looking_at(buf, &i, "\\   ")) {
3433                 if (prevColor != ColorNormal) {
3434                     if (oldi > next_out) {
3435                         SendToPlayer(&buf[next_out], oldi - next_out);
3436                         next_out = oldi;
3437                     }
3438                     Colorize(prevColor, TRUE);
3439                     curColor = prevColor;
3440                 }
3441                 if (savingComment) {
3442                     parse_pos = i - oldi;
3443                     memcpy(parse, &buf[oldi], parse_pos);
3444                     parse[parse_pos] = NULLCHAR;
3445                     started = STARTED_COMMENT;
3446                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3447                         chattingPartner = savingComment - 3; // kludge to remember the box
3448                 } else {
3449                     started = STARTED_CHATTER;
3450                 }
3451                 continue;
3452             }
3453
3454             if (looking_at(buf, &i, "Black Strength :") ||
3455                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3456                 looking_at(buf, &i, "<10>") ||
3457                 looking_at(buf, &i, "#@#")) {
3458                 /* Wrong board style */
3459                 loggedOn = TRUE;
3460                 SendToICS(ics_prefix);
3461                 SendToICS("set style 12\n");
3462                 SendToICS(ics_prefix);
3463                 SendToICS("refresh\n");
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "login:")) {
3468               if (!have_sent_ICS_logon) {
3469                 if(ICSInitScript())
3470                   have_sent_ICS_logon = 1;
3471                 else // no init script was found
3472                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3473               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3474                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3475               }
3476                 continue;
3477             }
3478
3479             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3480                 (looking_at(buf, &i, "\n<12> ") ||
3481                  looking_at(buf, &i, "<12> "))) {
3482                 loggedOn = TRUE;
3483                 if (oldi > next_out) {
3484                     SendToPlayer(&buf[next_out], oldi - next_out);
3485                 }
3486                 next_out = i;
3487                 started = STARTED_BOARD;
3488                 parse_pos = 0;
3489                 continue;
3490             }
3491
3492             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3493                 looking_at(buf, &i, "<b1> ")) {
3494                 if (oldi > next_out) {
3495                     SendToPlayer(&buf[next_out], oldi - next_out);
3496                 }
3497                 next_out = i;
3498                 started = STARTED_HOLDINGS;
3499                 parse_pos = 0;
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3504                 loggedOn = TRUE;
3505                 /* Header for a move list -- first line */
3506
3507                 switch (ics_getting_history) {
3508                   case H_FALSE:
3509                     switch (gameMode) {
3510                       case IcsIdle:
3511                       case BeginningOfGame:
3512                         /* User typed "moves" or "oldmoves" while we
3513                            were idle.  Pretend we asked for these
3514                            moves and soak them up so user can step
3515                            through them and/or save them.
3516                            */
3517                         Reset(FALSE, TRUE);
3518                         gameMode = IcsObserving;
3519                         ModeHighlight();
3520                         ics_gamenum = -1;
3521                         ics_getting_history = H_GOT_UNREQ_HEADER;
3522                         break;
3523                       case EditGame: /*?*/
3524                       case EditPosition: /*?*/
3525                         /* Should above feature work in these modes too? */
3526                         /* For now it doesn't */
3527                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3528                         break;
3529                       default:
3530                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3531                         break;
3532                     }
3533                     break;
3534                   case H_REQUESTED:
3535                     /* Is this the right one? */
3536                     if (gameInfo.white && gameInfo.black &&
3537                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3538                         strcmp(gameInfo.black, star_match[2]) == 0) {
3539                         /* All is well */
3540                         ics_getting_history = H_GOT_REQ_HEADER;
3541                     }
3542                     break;
3543                   case H_GOT_REQ_HEADER:
3544                   case H_GOT_UNREQ_HEADER:
3545                   case H_GOT_UNWANTED_HEADER:
3546                   case H_GETTING_MOVES:
3547                     /* Should not happen */
3548                     DisplayError(_("Error gathering move list: two headers"), 0);
3549                     ics_getting_history = H_FALSE;
3550                     break;
3551                 }
3552
3553                 /* Save player ratings into gameInfo if needed */
3554                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3555                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3556                     (gameInfo.whiteRating == -1 ||
3557                      gameInfo.blackRating == -1)) {
3558
3559                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3560                     gameInfo.blackRating = string_to_rating(star_match[3]);
3561                     if (appData.debugMode)
3562                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3563                               gameInfo.whiteRating, gameInfo.blackRating);
3564                 }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i,
3569               "* * match, initial time: * minute*, increment: * second")) {
3570                 /* Header for a move list -- second line */
3571                 /* Initial board will follow if this is a wild game */
3572                 if (gameInfo.event != NULL) free(gameInfo.event);
3573                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3574                 gameInfo.event = StrSave(str);
3575                 /* [HGM] we switched variant. Translate boards if needed. */
3576                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3577                 continue;
3578             }
3579
3580             if (looking_at(buf, &i, "Move  ")) {
3581                 /* Beginning of a move list */
3582                 switch (ics_getting_history) {
3583                   case H_FALSE:
3584                     /* Normally should not happen */
3585                     /* Maybe user hit reset while we were parsing */
3586                     break;
3587                   case H_REQUESTED:
3588                     /* Happens if we are ignoring a move list that is not
3589                      * the one we just requested.  Common if the user
3590                      * tries to observe two games without turning off
3591                      * getMoveList */
3592                     break;
3593                   case H_GETTING_MOVES:
3594                     /* Should not happen */
3595                     DisplayError(_("Error gathering move list: nested"), 0);
3596                     ics_getting_history = H_FALSE;
3597                     break;
3598                   case H_GOT_REQ_HEADER:
3599                     ics_getting_history = H_GETTING_MOVES;
3600                     started = STARTED_MOVES;
3601                     parse_pos = 0;
3602                     if (oldi > next_out) {
3603                         SendToPlayer(&buf[next_out], oldi - next_out);
3604                     }
3605                     break;
3606                   case H_GOT_UNREQ_HEADER:
3607                     ics_getting_history = H_GETTING_MOVES;
3608                     started = STARTED_MOVES_NOHIDE;
3609                     parse_pos = 0;
3610                     break;
3611                   case H_GOT_UNWANTED_HEADER:
3612                     ics_getting_history = H_FALSE;
3613                     break;
3614                 }
3615                 continue;
3616             }
3617
3618             if (looking_at(buf, &i, "% ") ||
3619                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3620                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3621                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3622                     soughtPending = FALSE;
3623                     seekGraphUp = TRUE;
3624                     DrawSeekGraph();
3625                 }
3626                 if(suppressKibitz) next_out = i;
3627                 savingComment = FALSE;
3628                 suppressKibitz = 0;
3629                 switch (started) {
3630                   case STARTED_MOVES:
3631                   case STARTED_MOVES_NOHIDE:
3632                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3633                     parse[parse_pos + i - oldi] = NULLCHAR;
3634                     ParseGameHistory(parse);
3635 #if ZIPPY
3636                     if (appData.zippyPlay && first.initDone) {
3637                         FeedMovesToProgram(&first, forwardMostMove);
3638                         if (gameMode == IcsPlayingWhite) {
3639                             if (WhiteOnMove(forwardMostMove)) {
3640                                 if (first.sendTime) {
3641                                   if (first.useColors) {
3642                                     SendToProgram("black\n", &first);
3643                                   }
3644                                   SendTimeRemaining(&first, TRUE);
3645                                 }
3646                                 if (first.useColors) {
3647                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3648                                 }
3649                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3650                                 first.maybeThinking = TRUE;
3651                             } else {
3652                                 if (first.usePlayother) {
3653                                   if (first.sendTime) {
3654                                     SendTimeRemaining(&first, TRUE);
3655                                   }
3656                                   SendToProgram("playother\n", &first);
3657                                   firstMove = FALSE;
3658                                 } else {
3659                                   firstMove = TRUE;
3660                                 }
3661                             }
3662                         } else if (gameMode == IcsPlayingBlack) {
3663                             if (!WhiteOnMove(forwardMostMove)) {
3664                                 if (first.sendTime) {
3665                                   if (first.useColors) {
3666                                     SendToProgram("white\n", &first);
3667                                   }
3668                                   SendTimeRemaining(&first, FALSE);
3669                                 }
3670                                 if (first.useColors) {
3671                                   SendToProgram("black\n", &first);
3672                                 }
3673                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3674                                 first.maybeThinking = TRUE;
3675                             } else {
3676                                 if (first.usePlayother) {
3677                                   if (first.sendTime) {
3678                                     SendTimeRemaining(&first, FALSE);
3679                                   }
3680                                   SendToProgram("playother\n", &first);
3681                                   firstMove = FALSE;
3682                                 } else {
3683                                   firstMove = TRUE;
3684                                 }
3685                             }
3686                         }
3687                     }
3688 #endif
3689                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3690                         /* Moves came from oldmoves or moves command
3691                            while we weren't doing anything else.
3692                            */
3693                         currentMove = forwardMostMove;
3694                         ClearHighlights();/*!!could figure this out*/
3695                         flipView = appData.flipView;
3696                         DrawPosition(TRUE, boards[currentMove]);
3697                         DisplayBothClocks();
3698                         snprintf(str, MSG_SIZ, "%s %s %s",
3699                                 gameInfo.white, _("vs."),  gameInfo.black);
3700                         DisplayTitle(str);
3701                         gameMode = IcsIdle;
3702                     } else {
3703                         /* Moves were history of an active game */
3704                         if (gameInfo.resultDetails != NULL) {
3705                             free(gameInfo.resultDetails);
3706                             gameInfo.resultDetails = NULL;
3707                         }
3708                     }
3709                     HistorySet(parseList, backwardMostMove,
3710                                forwardMostMove, currentMove-1);
3711                     DisplayMove(currentMove - 1);
3712                     if (started == STARTED_MOVES) next_out = i;
3713                     started = STARTED_NONE;
3714                     ics_getting_history = H_FALSE;
3715                     break;
3716
3717                   case STARTED_OBSERVE:
3718                     started = STARTED_NONE;
3719                     SendToICS(ics_prefix);
3720                     SendToICS("refresh\n");
3721                     break;
3722
3723                   default:
3724                     break;
3725                 }
3726                 if(bookHit) { // [HGM] book: simulate book reply
3727                     static char bookMove[MSG_SIZ]; // a bit generous?
3728
3729                     programStats.nodes = programStats.depth = programStats.time =
3730                     programStats.score = programStats.got_only_move = 0;
3731                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3732
3733                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3734                     strcat(bookMove, bookHit);
3735                     HandleMachineMove(bookMove, &first);
3736                 }
3737                 continue;
3738             }
3739
3740             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3741                  started == STARTED_HOLDINGS ||
3742                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3743                 /* Accumulate characters in move list or board */
3744                 parse[parse_pos++] = buf[i];
3745             }
3746
3747             /* Start of game messages.  Mostly we detect start of game
3748                when the first board image arrives.  On some versions
3749                of the ICS, though, we need to do a "refresh" after starting
3750                to observe in order to get the current board right away. */
3751             if (looking_at(buf, &i, "Adding game * to observation list")) {
3752                 started = STARTED_OBSERVE;
3753                 continue;
3754             }
3755
3756             /* Handle auto-observe */
3757             if (appData.autoObserve &&
3758                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3759                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3760                 char *player;
3761                 /* Choose the player that was highlighted, if any. */
3762                 if (star_match[0][0] == '\033' ||
3763                     star_match[1][0] != '\033') {
3764                     player = star_match[0];
3765                 } else {
3766                     player = star_match[2];
3767                 }
3768                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3769                         ics_prefix, StripHighlightAndTitle(player));
3770                 SendToICS(str);
3771
3772                 /* Save ratings from notify string */
3773                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3774                 player1Rating = string_to_rating(star_match[1]);
3775                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3776                 player2Rating = string_to_rating(star_match[3]);
3777
3778                 if (appData.debugMode)
3779                   fprintf(debugFP,
3780                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3781                           player1Name, player1Rating,
3782                           player2Name, player2Rating);
3783
3784                 continue;
3785             }
3786
3787             /* Deal with automatic examine mode after a game,
3788                and with IcsObserving -> IcsExamining transition */
3789             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3790                 looking_at(buf, &i, "has made you an examiner of game *")) {
3791
3792                 int gamenum = atoi(star_match[0]);
3793                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3794                     gamenum == ics_gamenum) {
3795                     /* We were already playing or observing this game;
3796                        no need to refetch history */
3797                     gameMode = IcsExamining;
3798                     if (pausing) {
3799                         pauseExamForwardMostMove = forwardMostMove;
3800                     } else if (currentMove < forwardMostMove) {
3801                         ForwardInner(forwardMostMove);
3802                     }
3803                 } else {
3804                     /* I don't think this case really can happen */
3805                     SendToICS(ics_prefix);
3806                     SendToICS("refresh\n");
3807                 }
3808                 continue;
3809             }
3810
3811             /* Error messages */
3812 //          if (ics_user_moved) {
3813             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3814                 if (looking_at(buf, &i, "Illegal move") ||
3815                     looking_at(buf, &i, "Not a legal move") ||
3816                     looking_at(buf, &i, "Your king is in check") ||
3817                     looking_at(buf, &i, "It isn't your turn") ||
3818                     looking_at(buf, &i, "It is not your move")) {
3819                     /* Illegal move */
3820                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3821                         currentMove = forwardMostMove-1;
3822                         DisplayMove(currentMove - 1); /* before DMError */
3823                         DrawPosition(FALSE, boards[currentMove]);
3824                         SwitchClocks(forwardMostMove-1); // [HGM] race
3825                         DisplayBothClocks();
3826                     }
3827                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3828                     ics_user_moved = 0;
3829                     continue;
3830                 }
3831             }
3832
3833             if (looking_at(buf, &i, "still have time") ||
3834                 looking_at(buf, &i, "not out of time") ||
3835                 looking_at(buf, &i, "either player is out of time") ||
3836                 looking_at(buf, &i, "has timeseal; checking")) {
3837                 /* We must have called his flag a little too soon */
3838                 whiteFlag = blackFlag = FALSE;
3839                 continue;
3840             }
3841
3842             if (looking_at(buf, &i, "added * seconds to") ||
3843                 looking_at(buf, &i, "seconds were added to")) {
3844                 /* Update the clocks */
3845                 SendToICS(ics_prefix);
3846                 SendToICS("refresh\n");
3847                 continue;
3848             }
3849
3850             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3851                 ics_clock_paused = TRUE;
3852                 StopClocks();
3853                 continue;
3854             }
3855
3856             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3857                 ics_clock_paused = FALSE;
3858                 StartClocks();
3859                 continue;
3860             }
3861
3862             /* Grab player ratings from the Creating: message.
3863                Note we have to check for the special case when
3864                the ICS inserts things like [white] or [black]. */
3865             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3866                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3867                 /* star_matches:
3868                    0    player 1 name (not necessarily white)
3869                    1    player 1 rating
3870                    2    empty, white, or black (IGNORED)
3871                    3    player 2 name (not necessarily black)
3872                    4    player 2 rating
3873
3874                    The names/ratings are sorted out when the game
3875                    actually starts (below).
3876                 */
3877                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3878                 player1Rating = string_to_rating(star_match[1]);
3879                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3880                 player2Rating = string_to_rating(star_match[4]);
3881
3882                 if (appData.debugMode)
3883                   fprintf(debugFP,
3884                           "Ratings from 'Creating:' %s %d, %s %d\n",
3885                           player1Name, player1Rating,
3886                           player2Name, player2Rating);
3887
3888                 continue;
3889             }
3890
3891             /* Improved generic start/end-of-game messages */
3892             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3893                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3894                 /* If tkind == 0: */
3895                 /* star_match[0] is the game number */
3896                 /*           [1] is the white player's name */
3897                 /*           [2] is the black player's name */
3898                 /* For end-of-game: */
3899                 /*           [3] is the reason for the game end */
3900                 /*           [4] is a PGN end game-token, preceded by " " */
3901                 /* For start-of-game: */
3902                 /*           [3] begins with "Creating" or "Continuing" */
3903                 /*           [4] is " *" or empty (don't care). */
3904                 int gamenum = atoi(star_match[0]);
3905                 char *whitename, *blackname, *why, *endtoken;
3906                 ChessMove endtype = EndOfFile;
3907
3908                 if (tkind == 0) {
3909                   whitename = star_match[1];
3910                   blackname = star_match[2];
3911                   why = star_match[3];
3912                   endtoken = star_match[4];
3913                 } else {
3914                   whitename = star_match[1];
3915                   blackname = star_match[3];
3916                   why = star_match[5];
3917                   endtoken = star_match[6];
3918                 }
3919
3920                 /* Game start messages */
3921                 if (strncmp(why, "Creating ", 9) == 0 ||
3922                     strncmp(why, "Continuing ", 11) == 0) {
3923                     gs_gamenum = gamenum;
3924                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3925                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3926                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3927 #if ZIPPY
3928                     if (appData.zippyPlay) {
3929                         ZippyGameStart(whitename, blackname);
3930                     }
3931 #endif /*ZIPPY*/
3932                     partnerBoardValid = FALSE; // [HGM] bughouse
3933                     continue;
3934                 }
3935
3936                 /* Game end messages */
3937                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3938                     ics_gamenum != gamenum) {
3939                     continue;
3940                 }
3941                 while (endtoken[0] == ' ') endtoken++;
3942                 switch (endtoken[0]) {
3943                   case '*':
3944                   default:
3945                     endtype = GameUnfinished;
3946                     break;
3947                   case '0':
3948                     endtype = BlackWins;
3949                     break;
3950                   case '1':
3951                     if (endtoken[1] == '/')
3952                       endtype = GameIsDrawn;
3953                     else
3954                       endtype = WhiteWins;
3955                     break;
3956                 }
3957                 GameEnds(endtype, why, GE_ICS);
3958 #if ZIPPY
3959                 if (appData.zippyPlay && first.initDone) {
3960                     ZippyGameEnd(endtype, why);
3961                     if (first.pr == NoProc) {
3962                       /* Start the next process early so that we'll
3963                          be ready for the next challenge */
3964                       StartChessProgram(&first);
3965                     }
3966                     /* Send "new" early, in case this command takes
3967                        a long time to finish, so that we'll be ready
3968                        for the next challenge. */
3969                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3970                     Reset(TRUE, TRUE);
3971                 }
3972 #endif /*ZIPPY*/
3973                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3974                 continue;
3975             }
3976
3977             if (looking_at(buf, &i, "Removing game * from observation") ||
3978                 looking_at(buf, &i, "no longer observing game *") ||
3979                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3980                 if (gameMode == IcsObserving &&
3981                     atoi(star_match[0]) == ics_gamenum)
3982                   {
3983                       /* icsEngineAnalyze */
3984                       if (appData.icsEngineAnalyze) {
3985                             ExitAnalyzeMode();
3986                             ModeHighlight();
3987                       }
3988                       StopClocks();
3989                       gameMode = IcsIdle;
3990                       ics_gamenum = -1;
3991                       ics_user_moved = FALSE;
3992                   }
3993                 continue;
3994             }
3995
3996             if (looking_at(buf, &i, "no longer examining game *")) {
3997                 if (gameMode == IcsExamining &&
3998                     atoi(star_match[0]) == ics_gamenum)
3999                   {
4000                       gameMode = IcsIdle;
4001                       ics_gamenum = -1;
4002                       ics_user_moved = FALSE;
4003                   }
4004                 continue;
4005             }
4006
4007             /* Advance leftover_start past any newlines we find,
4008                so only partial lines can get reparsed */
4009             if (looking_at(buf, &i, "\n")) {
4010                 prevColor = curColor;
4011                 if (curColor != ColorNormal) {
4012                     if (oldi > next_out) {
4013                         SendToPlayer(&buf[next_out], oldi - next_out);
4014                         next_out = oldi;
4015                     }
4016                     Colorize(ColorNormal, FALSE);
4017                     curColor = ColorNormal;
4018                 }
4019                 if (started == STARTED_BOARD) {
4020                     started = STARTED_NONE;
4021                     parse[parse_pos] = NULLCHAR;
4022                     ParseBoard12(parse);
4023                     ics_user_moved = 0;
4024
4025                     /* Send premove here */
4026                     if (appData.premove) {
4027                       char str[MSG_SIZ];
4028                       if (currentMove == 0 &&
4029                           gameMode == IcsPlayingWhite &&
4030                           appData.premoveWhite) {
4031                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4032                         if (appData.debugMode)
4033                           fprintf(debugFP, "Sending premove:\n");
4034                         SendToICS(str);
4035                       } else if (currentMove == 1 &&
4036                                  gameMode == IcsPlayingBlack &&
4037                                  appData.premoveBlack) {
4038                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4039                         if (appData.debugMode)
4040                           fprintf(debugFP, "Sending premove:\n");
4041                         SendToICS(str);
4042                       } else if (gotPremove) {
4043                         gotPremove = 0;
4044                         ClearPremoveHighlights();
4045                         if (appData.debugMode)
4046                           fprintf(debugFP, "Sending premove:\n");
4047                           UserMoveEvent(premoveFromX, premoveFromY,
4048                                         premoveToX, premoveToY,
4049                                         premovePromoChar);
4050                       }
4051                     }
4052
4053                     /* Usually suppress following prompt */
4054                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4055                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4056                         if (looking_at(buf, &i, "*% ")) {
4057                             savingComment = FALSE;
4058                             suppressKibitz = 0;
4059                         }
4060                     }
4061                     next_out = i;
4062                 } else if (started == STARTED_HOLDINGS) {
4063                     int gamenum;
4064                     char new_piece[MSG_SIZ];
4065                     started = STARTED_NONE;
4066                     parse[parse_pos] = NULLCHAR;
4067                     if (appData.debugMode)
4068                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4069                                                         parse, currentMove);
4070                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4071                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4072                         if (gameInfo.variant == VariantNormal) {
4073                           /* [HGM] We seem to switch variant during a game!
4074                            * Presumably no holdings were displayed, so we have
4075                            * to move the position two files to the right to
4076                            * create room for them!
4077                            */
4078                           VariantClass newVariant;
4079                           switch(gameInfo.boardWidth) { // base guess on board width
4080                                 case 9:  newVariant = VariantShogi; break;
4081                                 case 10: newVariant = VariantGreat; break;
4082                                 default: newVariant = VariantCrazyhouse; break;
4083                           }
4084                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4085                           /* Get a move list just to see the header, which
4086                              will tell us whether this is really bug or zh */
4087                           if (ics_getting_history == H_FALSE) {
4088                             ics_getting_history = H_REQUESTED;
4089                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4090                             SendToICS(str);
4091                           }
4092                         }
4093                         new_piece[0] = NULLCHAR;
4094                         sscanf(parse, "game %d white [%s black [%s <- %s",
4095                                &gamenum, white_holding, black_holding,
4096                                new_piece);
4097                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4098                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4099                         /* [HGM] copy holdings to board holdings area */
4100                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4101                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4102                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4103 #if ZIPPY
4104                         if (appData.zippyPlay && first.initDone) {
4105                             ZippyHoldings(white_holding, black_holding,
4106                                           new_piece);
4107                         }
4108 #endif /*ZIPPY*/
4109                         if (tinyLayout || smallLayout) {
4110                             char wh[16], bh[16];
4111                             PackHolding(wh, white_holding);
4112                             PackHolding(bh, black_holding);
4113                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4114                                     gameInfo.white, gameInfo.black);
4115                         } else {
4116                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4117                                     gameInfo.white, white_holding, _("vs."),
4118                                     gameInfo.black, black_holding);
4119                         }
4120                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4121                         DrawPosition(FALSE, boards[currentMove]);
4122                         DisplayTitle(str);
4123                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4124                         sscanf(parse, "game %d white [%s black [%s <- %s",
4125                                &gamenum, white_holding, black_holding,
4126                                new_piece);
4127                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4128                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4129                         /* [HGM] copy holdings to partner-board holdings area */
4130                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4131                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4132                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4133                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4134                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4135                       }
4136                     }
4137                     /* Suppress following prompt */
4138                     if (looking_at(buf, &i, "*% ")) {
4139                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4140                         savingComment = FALSE;
4141                         suppressKibitz = 0;
4142                     }
4143                     next_out = i;
4144                 }
4145                 continue;
4146             }
4147
4148             i++;                /* skip unparsed character and loop back */
4149         }
4150
4151         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4152 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4153 //          SendToPlayer(&buf[next_out], i - next_out);
4154             started != STARTED_HOLDINGS && leftover_start > next_out) {
4155             SendToPlayer(&buf[next_out], leftover_start - next_out);
4156             next_out = i;
4157         }
4158
4159         leftover_len = buf_len - leftover_start;
4160         /* if buffer ends with something we couldn't parse,
4161            reparse it after appending the next read */
4162
4163     } else if (count == 0) {
4164         RemoveInputSource(isr);
4165         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4166     } else {
4167         DisplayFatalError(_("Error reading from ICS"), error, 1);
4168     }
4169 }
4170
4171
4172 /* Board style 12 looks like this:
4173
4174    <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
4175
4176  * The "<12> " is stripped before it gets to this routine.  The two
4177  * trailing 0's (flip state and clock ticking) are later addition, and
4178  * some chess servers may not have them, or may have only the first.
4179  * Additional trailing fields may be added in the future.
4180  */
4181
4182 #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"
4183
4184 #define RELATION_OBSERVING_PLAYED    0
4185 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4186 #define RELATION_PLAYING_MYMOVE      1
4187 #define RELATION_PLAYING_NOTMYMOVE  -1
4188 #define RELATION_EXAMINING           2
4189 #define RELATION_ISOLATED_BOARD     -3
4190 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4191
4192 void
4193 ParseBoard12 (char *string)
4194 {
4195 #if ZIPPY
4196     int i, takeback;
4197     char *bookHit = NULL; // [HGM] book
4198 #endif
4199     GameMode newGameMode;
4200     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4201     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4202     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4203     char to_play, board_chars[200];
4204     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4205     char black[32], white[32];
4206     Board board;
4207     int prevMove = currentMove;
4208     int ticking = 2;
4209     ChessMove moveType;
4210     int fromX, fromY, toX, toY;
4211     char promoChar;
4212     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4213     Boolean weird = FALSE, reqFlag = FALSE;
4214
4215     fromX = fromY = toX = toY = -1;
4216
4217     newGame = FALSE;
4218
4219     if (appData.debugMode)
4220       fprintf(debugFP, "Parsing board: %s\n", string);
4221
4222     move_str[0] = NULLCHAR;
4223     elapsed_time[0] = NULLCHAR;
4224     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4225         int  i = 0, j;
4226         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4227             if(string[i] == ' ') { ranks++; files = 0; }
4228             else files++;
4229             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4230             i++;
4231         }
4232         for(j = 0; j <i; j++) board_chars[j] = string[j];
4233         board_chars[i] = '\0';
4234         string += i + 1;
4235     }
4236     n = sscanf(string, PATTERN, &to_play, &double_push,
4237                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4238                &gamenum, white, black, &relation, &basetime, &increment,
4239                &white_stren, &black_stren, &white_time, &black_time,
4240                &moveNum, str, elapsed_time, move_str, &ics_flip,
4241                &ticking);
4242
4243     if (n < 21) {
4244         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4245         DisplayError(str, 0);
4246         return;
4247     }
4248
4249     /* Convert the move number to internal form */
4250     moveNum = (moveNum - 1) * 2;
4251     if (to_play == 'B') moveNum++;
4252     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4253       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4254                         0, 1);
4255       return;
4256     }
4257
4258     switch (relation) {
4259       case RELATION_OBSERVING_PLAYED:
4260       case RELATION_OBSERVING_STATIC:
4261         if (gamenum == -1) {
4262             /* Old ICC buglet */
4263             relation = RELATION_OBSERVING_STATIC;
4264         }
4265         newGameMode = IcsObserving;
4266         break;
4267       case RELATION_PLAYING_MYMOVE:
4268       case RELATION_PLAYING_NOTMYMOVE:
4269         newGameMode =
4270           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4271             IcsPlayingWhite : IcsPlayingBlack;
4272         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4273         break;
4274       case RELATION_EXAMINING:
4275         newGameMode = IcsExamining;
4276         break;
4277       case RELATION_ISOLATED_BOARD:
4278       default:
4279         /* Just display this board.  If user was doing something else,
4280            we will forget about it until the next board comes. */
4281         newGameMode = IcsIdle;
4282         break;
4283       case RELATION_STARTING_POSITION:
4284         newGameMode = gameMode;
4285         break;
4286     }
4287
4288     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4289         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4290          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4291       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4292       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4293       static int lastBgGame = -1;
4294       char *toSqr;
4295       for (k = 0; k < ranks; k++) {
4296         for (j = 0; j < files; j++)
4297           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4298         if(gameInfo.holdingsWidth > 1) {
4299              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4300              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4301         }
4302       }
4303       CopyBoard(partnerBoard, board);
4304       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4305         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4306         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4307       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4308       if(toSqr = strchr(str, '-')) {
4309         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4310         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4311       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4312       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4313       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4314       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4315       if(twoBoards) {
4316           DisplayWhiteClock(white_time*fac, to_play == 'W');
4317           DisplayBlackClock(black_time*fac, to_play != 'W');
4318           activePartner = to_play;
4319           if(gamenum != lastBgGame) {
4320               char buf[MSG_SIZ];
4321               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4322               DisplayTitle(buf);
4323           }
4324           lastBgGame = gamenum;
4325           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4326                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4327       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4328                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4329       if(!twoBoards) DisplayMessage(partnerStatus, "");
4330         partnerBoardValid = TRUE;
4331       return;
4332     }
4333
4334     if(appData.dualBoard && appData.bgObserve) {
4335         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4336             SendToICS(ics_prefix), SendToICS("pobserve\n");
4337         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4338             char buf[MSG_SIZ];
4339             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4340             SendToICS(buf);
4341         }
4342     }
4343
4344     /* Modify behavior for initial board display on move listing
4345        of wild games.
4346        */
4347     switch (ics_getting_history) {
4348       case H_FALSE:
4349       case H_REQUESTED:
4350         break;
4351       case H_GOT_REQ_HEADER:
4352       case H_GOT_UNREQ_HEADER:
4353         /* This is the initial position of the current game */
4354         gamenum = ics_gamenum;
4355         moveNum = 0;            /* old ICS bug workaround */
4356         if (to_play == 'B') {
4357           startedFromSetupPosition = TRUE;
4358           blackPlaysFirst = TRUE;
4359           moveNum = 1;
4360           if (forwardMostMove == 0) forwardMostMove = 1;
4361           if (backwardMostMove == 0) backwardMostMove = 1;
4362           if (currentMove == 0) currentMove = 1;
4363         }
4364         newGameMode = gameMode;
4365         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4366         break;
4367       case H_GOT_UNWANTED_HEADER:
4368         /* This is an initial board that we don't want */
4369         return;
4370       case H_GETTING_MOVES:
4371         /* Should not happen */
4372         DisplayError(_("Error gathering move list: extra board"), 0);
4373         ics_getting_history = H_FALSE;
4374         return;
4375     }
4376
4377    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4378                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4379                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4380      /* [HGM] We seem to have switched variant unexpectedly
4381       * Try to guess new variant from board size
4382       */
4383           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4384           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4385           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4386           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4387           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4388           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4389           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4390           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4391           /* Get a move list just to see the header, which
4392              will tell us whether this is really bug or zh */
4393           if (ics_getting_history == H_FALSE) {
4394             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4395             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4396             SendToICS(str);
4397           }
4398     }
4399
4400     /* Take action if this is the first board of a new game, or of a
4401        different game than is currently being displayed.  */
4402     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4403         relation == RELATION_ISOLATED_BOARD) {
4404
4405         /* Forget the old game and get the history (if any) of the new one */
4406         if (gameMode != BeginningOfGame) {
4407           Reset(TRUE, TRUE);
4408         }
4409         newGame = TRUE;
4410         if (appData.autoRaiseBoard) BoardToTop();
4411         prevMove = -3;
4412         if (gamenum == -1) {
4413             newGameMode = IcsIdle;
4414         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4415                    appData.getMoveList && !reqFlag) {
4416             /* Need to get game history */
4417             ics_getting_history = H_REQUESTED;
4418             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4419             SendToICS(str);
4420         }
4421
4422         /* Initially flip the board to have black on the bottom if playing
4423            black or if the ICS flip flag is set, but let the user change
4424            it with the Flip View button. */
4425         flipView = appData.autoFlipView ?
4426           (newGameMode == IcsPlayingBlack) || ics_flip :
4427           appData.flipView;
4428
4429         /* Done with values from previous mode; copy in new ones */
4430         gameMode = newGameMode;
4431         ModeHighlight();
4432         ics_gamenum = gamenum;
4433         if (gamenum == gs_gamenum) {
4434             int klen = strlen(gs_kind);
4435             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4436             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4437             gameInfo.event = StrSave(str);
4438         } else {
4439             gameInfo.event = StrSave("ICS game");
4440         }
4441         gameInfo.site = StrSave(appData.icsHost);
4442         gameInfo.date = PGNDate();
4443         gameInfo.round = StrSave("-");
4444         gameInfo.white = StrSave(white);
4445         gameInfo.black = StrSave(black);
4446         timeControl = basetime * 60 * 1000;
4447         timeControl_2 = 0;
4448         timeIncrement = increment * 1000;
4449         movesPerSession = 0;
4450         gameInfo.timeControl = TimeControlTagValue();
4451         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4452   if (appData.debugMode) {
4453     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4454     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4455     setbuf(debugFP, NULL);
4456   }
4457
4458         gameInfo.outOfBook = NULL;
4459
4460         /* Do we have the ratings? */
4461         if (strcmp(player1Name, white) == 0 &&
4462             strcmp(player2Name, black) == 0) {
4463             if (appData.debugMode)
4464               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4465                       player1Rating, player2Rating);
4466             gameInfo.whiteRating = player1Rating;
4467             gameInfo.blackRating = player2Rating;
4468         } else if (strcmp(player2Name, white) == 0 &&
4469                    strcmp(player1Name, black) == 0) {
4470             if (appData.debugMode)
4471               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4472                       player2Rating, player1Rating);
4473             gameInfo.whiteRating = player2Rating;
4474             gameInfo.blackRating = player1Rating;
4475         }
4476         player1Name[0] = player2Name[0] = NULLCHAR;
4477
4478         /* Silence shouts if requested */
4479         if (appData.quietPlay &&
4480             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4481             SendToICS(ics_prefix);
4482             SendToICS("set shout 0\n");
4483         }
4484     }
4485
4486     /* Deal with midgame name changes */
4487     if (!newGame) {
4488         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4489             if (gameInfo.white) free(gameInfo.white);
4490             gameInfo.white = StrSave(white);
4491         }
4492         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4493             if (gameInfo.black) free(gameInfo.black);
4494             gameInfo.black = StrSave(black);
4495         }
4496     }
4497
4498     /* Throw away game result if anything actually changes in examine mode */
4499     if (gameMode == IcsExamining && !newGame) {
4500         gameInfo.result = GameUnfinished;
4501         if (gameInfo.resultDetails != NULL) {
4502             free(gameInfo.resultDetails);
4503             gameInfo.resultDetails = NULL;
4504         }
4505     }
4506
4507     /* In pausing && IcsExamining mode, we ignore boards coming
4508        in if they are in a different variation than we are. */
4509     if (pauseExamInvalid) return;
4510     if (pausing && gameMode == IcsExamining) {
4511         if (moveNum <= pauseExamForwardMostMove) {
4512             pauseExamInvalid = TRUE;
4513             forwardMostMove = pauseExamForwardMostMove;
4514             return;
4515         }
4516     }
4517
4518   if (appData.debugMode) {
4519     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4520   }
4521     /* Parse the board */
4522     for (k = 0; k < ranks; k++) {
4523       for (j = 0; j < files; j++)
4524         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4525       if(gameInfo.holdingsWidth > 1) {
4526            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4527            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4528       }
4529     }
4530     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4531       board[5][BOARD_RGHT+1] = WhiteAngel;
4532       board[6][BOARD_RGHT+1] = WhiteMarshall;
4533       board[1][0] = BlackMarshall;
4534       board[2][0] = BlackAngel;
4535       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4536     }
4537     CopyBoard(boards[moveNum], board);
4538     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4539     if (moveNum == 0) {
4540         startedFromSetupPosition =
4541           !CompareBoards(board, initialPosition);
4542         if(startedFromSetupPosition)
4543             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4544     }
4545
4546     /* [HGM] Set castling rights. Take the outermost Rooks,
4547        to make it also work for FRC opening positions. Note that board12
4548        is really defective for later FRC positions, as it has no way to
4549        indicate which Rook can castle if they are on the same side of King.
4550        For the initial position we grant rights to the outermost Rooks,
4551        and remember thos rights, and we then copy them on positions
4552        later in an FRC game. This means WB might not recognize castlings with
4553        Rooks that have moved back to their original position as illegal,
4554        but in ICS mode that is not its job anyway.
4555     */
4556     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4557     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4558
4559         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4560             if(board[0][i] == WhiteRook) j = i;
4561         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4562         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4563             if(board[0][i] == WhiteRook) j = i;
4564         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4565         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4566             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4567         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4568         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4569             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4570         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4571
4572         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4573         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4574         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4575             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4576         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4577             if(board[BOARD_HEIGHT-1][k] == bKing)
4578                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4579         if(gameInfo.variant == VariantTwoKings) {
4580             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4581             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4582             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4583         }
4584     } else { int r;
4585         r = boards[moveNum][CASTLING][0] = initialRights[0];
4586         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4587         r = boards[moveNum][CASTLING][1] = initialRights[1];
4588         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4589         r = boards[moveNum][CASTLING][3] = initialRights[3];
4590         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4591         r = boards[moveNum][CASTLING][4] = initialRights[4];
4592         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4593         /* wildcastle kludge: always assume King has rights */
4594         r = boards[moveNum][CASTLING][2] = initialRights[2];
4595         r = boards[moveNum][CASTLING][5] = initialRights[5];
4596     }
4597     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4598     boards[moveNum][EP_STATUS] = EP_NONE;
4599     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4600     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4601     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4602
4603
4604     if (ics_getting_history == H_GOT_REQ_HEADER ||
4605         ics_getting_history == H_GOT_UNREQ_HEADER) {
4606         /* This was an initial position from a move list, not
4607            the current position */
4608         return;
4609     }
4610
4611     /* Update currentMove and known move number limits */
4612     newMove = newGame || moveNum > forwardMostMove;
4613
4614     if (newGame) {
4615         forwardMostMove = backwardMostMove = currentMove = moveNum;
4616         if (gameMode == IcsExamining && moveNum == 0) {
4617           /* Workaround for ICS limitation: we are not told the wild
4618              type when starting to examine a game.  But if we ask for
4619              the move list, the move list header will tell us */
4620             ics_getting_history = H_REQUESTED;
4621             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4622             SendToICS(str);
4623         }
4624     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4625                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4626 #if ZIPPY
4627         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4628         /* [HGM] applied this also to an engine that is silently watching        */
4629         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4630             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4631             gameInfo.variant == currentlyInitializedVariant) {
4632           takeback = forwardMostMove - moveNum;
4633           for (i = 0; i < takeback; i++) {
4634             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4635             SendToProgram("undo\n", &first);
4636           }
4637         }
4638 #endif
4639
4640         forwardMostMove = moveNum;
4641         if (!pausing || currentMove > forwardMostMove)
4642           currentMove = forwardMostMove;
4643     } else {
4644         /* New part of history that is not contiguous with old part */
4645         if (pausing && gameMode == IcsExamining) {
4646             pauseExamInvalid = TRUE;
4647             forwardMostMove = pauseExamForwardMostMove;
4648             return;
4649         }
4650         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4651 #if ZIPPY
4652             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4653                 // [HGM] when we will receive the move list we now request, it will be
4654                 // fed to the engine from the first move on. So if the engine is not
4655                 // in the initial position now, bring it there.
4656                 InitChessProgram(&first, 0);
4657             }
4658 #endif
4659             ics_getting_history = H_REQUESTED;
4660             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4661             SendToICS(str);
4662         }
4663         forwardMostMove = backwardMostMove = currentMove = moveNum;
4664     }
4665
4666     /* Update the clocks */
4667     if (strchr(elapsed_time, '.')) {
4668       /* Time is in ms */
4669       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4670       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4671     } else {
4672       /* Time is in seconds */
4673       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4674       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4675     }
4676
4677
4678 #if ZIPPY
4679     if (appData.zippyPlay && newGame &&
4680         gameMode != IcsObserving && gameMode != IcsIdle &&
4681         gameMode != IcsExamining)
4682       ZippyFirstBoard(moveNum, basetime, increment);
4683 #endif
4684
4685     /* Put the move on the move list, first converting
4686        to canonical algebraic form. */
4687     if (moveNum > 0) {
4688   if (appData.debugMode) {
4689     int f = forwardMostMove;
4690     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4691             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4692             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4693     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4694     fprintf(debugFP, "moveNum = %d\n", moveNum);
4695     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4696     setbuf(debugFP, NULL);
4697   }
4698         if (moveNum <= backwardMostMove) {
4699             /* We don't know what the board looked like before
4700                this move.  Punt. */
4701           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4702             strcat(parseList[moveNum - 1], " ");
4703             strcat(parseList[moveNum - 1], elapsed_time);
4704             moveList[moveNum - 1][0] = NULLCHAR;
4705         } else if (strcmp(move_str, "none") == 0) {
4706             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4707             /* Again, we don't know what the board looked like;
4708                this is really the start of the game. */
4709             parseList[moveNum - 1][0] = NULLCHAR;
4710             moveList[moveNum - 1][0] = NULLCHAR;
4711             backwardMostMove = moveNum;
4712             startedFromSetupPosition = TRUE;
4713             fromX = fromY = toX = toY = -1;
4714         } else {
4715           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4716           //                 So we parse the long-algebraic move string in stead of the SAN move
4717           int valid; char buf[MSG_SIZ], *prom;
4718
4719           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4720                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4721           // str looks something like "Q/a1-a2"; kill the slash
4722           if(str[1] == '/')
4723             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4724           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4725           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4726                 strcat(buf, prom); // long move lacks promo specification!
4727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4728                 if(appData.debugMode)
4729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4730                 safeStrCpy(move_str, buf, MSG_SIZ);
4731           }
4732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4733                                 &fromX, &fromY, &toX, &toY, &promoChar)
4734                || ParseOneMove(buf, moveNum - 1, &moveType,
4735                                 &fromX, &fromY, &toX, &toY, &promoChar);
4736           // end of long SAN patch
4737           if (valid) {
4738             (void) CoordsToAlgebraic(boards[moveNum - 1],
4739                                      PosFlags(moveNum - 1),
4740                                      fromY, fromX, toY, toX, promoChar,
4741                                      parseList[moveNum-1]);
4742             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4743               case MT_NONE:
4744               case MT_STALEMATE:
4745               default:
4746                 break;
4747               case MT_CHECK:
4748                 if(gameInfo.variant != VariantShogi)
4749                     strcat(parseList[moveNum - 1], "+");
4750                 break;
4751               case MT_CHECKMATE:
4752               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4753                 strcat(parseList[moveNum - 1], "#");
4754                 break;
4755             }
4756             strcat(parseList[moveNum - 1], " ");
4757             strcat(parseList[moveNum - 1], elapsed_time);
4758             /* currentMoveString is set as a side-effect of ParseOneMove */
4759             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4760             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4761             strcat(moveList[moveNum - 1], "\n");
4762
4763             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4764                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4765               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4766                 ChessSquare old, new = boards[moveNum][k][j];
4767                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4768                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4769                   if(old == new) continue;
4770                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4771                   else if(new == WhiteWazir || new == BlackWazir) {
4772                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4773                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4774                       else boards[moveNum][k][j] = old; // preserve type of Gold
4775                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4776                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4777               }
4778           } else {
4779             /* Move from ICS was illegal!?  Punt. */
4780             if (appData.debugMode) {
4781               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4782               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4783             }
4784             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4785             strcat(parseList[moveNum - 1], " ");
4786             strcat(parseList[moveNum - 1], elapsed_time);
4787             moveList[moveNum - 1][0] = NULLCHAR;
4788             fromX = fromY = toX = toY = -1;
4789           }
4790         }
4791   if (appData.debugMode) {
4792     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4793     setbuf(debugFP, NULL);
4794   }
4795
4796 #if ZIPPY
4797         /* Send move to chess program (BEFORE animating it). */
4798         if (appData.zippyPlay && !newGame && newMove &&
4799            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4800
4801             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4802                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4803                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4804                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4805                             move_str);
4806                     DisplayError(str, 0);
4807                 } else {
4808                     if (first.sendTime) {
4809                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4810                     }
4811                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4812                     if (firstMove && !bookHit) {
4813                         firstMove = FALSE;
4814                         if (first.useColors) {
4815                           SendToProgram(gameMode == IcsPlayingWhite ?
4816                                         "white\ngo\n" :
4817                                         "black\ngo\n", &first);
4818                         } else {
4819                           SendToProgram("go\n", &first);
4820                         }
4821                         first.maybeThinking = TRUE;
4822                     }
4823                 }
4824             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4825               if (moveList[moveNum - 1][0] == NULLCHAR) {
4826                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4827                 DisplayError(str, 0);
4828               } else {
4829                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4830                 SendMoveToProgram(moveNum - 1, &first);
4831               }
4832             }
4833         }
4834 #endif
4835     }
4836
4837     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4838         /* If move comes from a remote source, animate it.  If it
4839            isn't remote, it will have already been animated. */
4840         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4841             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4842         }
4843         if (!pausing && appData.highlightLastMove) {
4844             SetHighlights(fromX, fromY, toX, toY);
4845         }
4846     }
4847
4848     /* Start the clocks */
4849     whiteFlag = blackFlag = FALSE;
4850     appData.clockMode = !(basetime == 0 && increment == 0);
4851     if (ticking == 0) {
4852       ics_clock_paused = TRUE;
4853       StopClocks();
4854     } else if (ticking == 1) {
4855       ics_clock_paused = FALSE;
4856     }
4857     if (gameMode == IcsIdle ||
4858         relation == RELATION_OBSERVING_STATIC ||
4859         relation == RELATION_EXAMINING ||
4860         ics_clock_paused)
4861       DisplayBothClocks();
4862     else
4863       StartClocks();
4864
4865     /* Display opponents and material strengths */
4866     if (gameInfo.variant != VariantBughouse &&
4867         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4868         if (tinyLayout || smallLayout) {
4869             if(gameInfo.variant == VariantNormal)
4870               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4871                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4872                     basetime, increment);
4873             else
4874               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4875                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4876                     basetime, increment, (int) gameInfo.variant);
4877         } else {
4878             if(gameInfo.variant == VariantNormal)
4879               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4880                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4881                     basetime, increment);
4882             else
4883               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4884                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4885                     basetime, increment, VariantName(gameInfo.variant));
4886         }
4887         DisplayTitle(str);
4888   if (appData.debugMode) {
4889     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4890   }
4891     }
4892
4893
4894     /* Display the board */
4895     if (!pausing && !appData.noGUI) {
4896
4897       if (appData.premove)
4898           if (!gotPremove ||
4899              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4900              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4901               ClearPremoveHighlights();
4902
4903       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4904         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4905       DrawPosition(j, boards[currentMove]);
4906
4907       DisplayMove(moveNum - 1);
4908       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4909             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4910               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4911         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4912       }
4913     }
4914
4915     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4916 #if ZIPPY
4917     if(bookHit) { // [HGM] book: simulate book reply
4918         static char bookMove[MSG_SIZ]; // a bit generous?
4919
4920         programStats.nodes = programStats.depth = programStats.time =
4921         programStats.score = programStats.got_only_move = 0;
4922         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4923
4924         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4925         strcat(bookMove, bookHit);
4926         HandleMachineMove(bookMove, &first);
4927     }
4928 #endif
4929 }
4930
4931 void
4932 GetMoveListEvent ()
4933 {
4934     char buf[MSG_SIZ];
4935     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4936         ics_getting_history = H_REQUESTED;
4937         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4938         SendToICS(buf);
4939     }
4940 }
4941
4942 void
4943 SendToBoth (char *msg)
4944 {   // to make it easy to keep two engines in step in dual analysis
4945     SendToProgram(msg, &first);
4946     if(second.analyzing) SendToProgram(msg, &second);
4947 }
4948
4949 void
4950 AnalysisPeriodicEvent (int force)
4951 {
4952     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4953          && !force) || !appData.periodicUpdates)
4954       return;
4955
4956     /* Send . command to Crafty to collect stats */
4957     SendToBoth(".\n");
4958
4959     /* Don't send another until we get a response (this makes
4960        us stop sending to old Crafty's which don't understand
4961        the "." command (sending illegal cmds resets node count & time,
4962        which looks bad)) */
4963     programStats.ok_to_send = 0;
4964 }
4965
4966 void
4967 ics_update_width (int new_width)
4968 {
4969         ics_printf("set width %d\n", new_width);
4970 }
4971
4972 void
4973 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4974 {
4975     char buf[MSG_SIZ];
4976
4977     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4978         // null move in variant where engine does not understand it (for analysis purposes)
4979         SendBoard(cps, moveNum + 1); // send position after move in stead.
4980         return;
4981     }
4982     if (cps->useUsermove) {
4983       SendToProgram("usermove ", cps);
4984     }
4985     if (cps->useSAN) {
4986       char *space;
4987       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4988         int len = space - parseList[moveNum];
4989         memcpy(buf, parseList[moveNum], len);
4990         buf[len++] = '\n';
4991         buf[len] = NULLCHAR;
4992       } else {
4993         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4994       }
4995       SendToProgram(buf, cps);
4996     } else {
4997       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4998         AlphaRank(moveList[moveNum], 4);
4999         SendToProgram(moveList[moveNum], cps);
5000         AlphaRank(moveList[moveNum], 4); // and back
5001       } else
5002       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5003        * the engine. It would be nice to have a better way to identify castle
5004        * moves here. */
5005       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5006                                                                          && cps->useOOCastle) {
5007         int fromX = moveList[moveNum][0] - AAA;
5008         int fromY = moveList[moveNum][1] - ONE;
5009         int toX = moveList[moveNum][2] - AAA;
5010         int toY = moveList[moveNum][3] - ONE;
5011         if((boards[moveNum][fromY][fromX] == WhiteKing
5012             && boards[moveNum][toY][toX] == WhiteRook)
5013            || (boards[moveNum][fromY][fromX] == BlackKing
5014                && boards[moveNum][toY][toX] == BlackRook)) {
5015           if(toX > fromX) SendToProgram("O-O\n", cps);
5016           else SendToProgram("O-O-O\n", cps);
5017         }
5018         else SendToProgram(moveList[moveNum], cps);
5019       } else
5020       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5021         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5022           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5023           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5024                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5025         } else
5026           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5027                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5028         SendToProgram(buf, cps);
5029       }
5030       else SendToProgram(moveList[moveNum], cps);
5031       /* End of additions by Tord */
5032     }
5033
5034     /* [HGM] setting up the opening has brought engine in force mode! */
5035     /*       Send 'go' if we are in a mode where machine should play. */
5036     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5037         (gameMode == TwoMachinesPlay   ||
5038 #if ZIPPY
5039          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5040 #endif
5041          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5042         SendToProgram("go\n", cps);
5043   if (appData.debugMode) {
5044     fprintf(debugFP, "(extra)\n");
5045   }
5046     }
5047     setboardSpoiledMachineBlack = 0;
5048 }
5049
5050 void
5051 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5052 {
5053     char user_move[MSG_SIZ];
5054     char suffix[4];
5055
5056     if(gameInfo.variant == VariantSChess && promoChar) {
5057         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5058         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5059     } else suffix[0] = NULLCHAR;
5060
5061     switch (moveType) {
5062       default:
5063         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5064                 (int)moveType, fromX, fromY, toX, toY);
5065         DisplayError(user_move + strlen("say "), 0);
5066         break;
5067       case WhiteKingSideCastle:
5068       case BlackKingSideCastle:
5069       case WhiteQueenSideCastleWild:
5070       case BlackQueenSideCastleWild:
5071       /* PUSH Fabien */
5072       case WhiteHSideCastleFR:
5073       case BlackHSideCastleFR:
5074       /* POP Fabien */
5075         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5076         break;
5077       case WhiteQueenSideCastle:
5078       case BlackQueenSideCastle:
5079       case WhiteKingSideCastleWild:
5080       case BlackKingSideCastleWild:
5081       /* PUSH Fabien */
5082       case WhiteASideCastleFR:
5083       case BlackASideCastleFR:
5084       /* POP Fabien */
5085         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5086         break;
5087       case WhiteNonPromotion:
5088       case BlackNonPromotion:
5089         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5090         break;
5091       case WhitePromotion:
5092       case BlackPromotion:
5093         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5094           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5095                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5096                 PieceToChar(WhiteFerz));
5097         else if(gameInfo.variant == VariantGreat)
5098           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5099                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5100                 PieceToChar(WhiteMan));
5101         else
5102           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5103                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5104                 promoChar);
5105         break;
5106       case WhiteDrop:
5107       case BlackDrop:
5108       drop:
5109         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5110                  ToUpper(PieceToChar((ChessSquare) fromX)),
5111                  AAA + toX, ONE + toY);
5112         break;
5113       case IllegalMove:  /* could be a variant we don't quite understand */
5114         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5115       case NormalMove:
5116       case WhiteCapturesEnPassant:
5117       case BlackCapturesEnPassant:
5118         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5119                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5120         break;
5121     }
5122     SendToICS(user_move);
5123     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5124         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5125 }
5126
5127 void
5128 UploadGameEvent ()
5129 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5130     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5131     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5132     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5133       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5134       return;
5135     }
5136     if(gameMode != IcsExamining) { // is this ever not the case?
5137         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5138
5139         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5140           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5141         } else { // on FICS we must first go to general examine mode
5142           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5143         }
5144         if(gameInfo.variant != VariantNormal) {
5145             // try figure out wild number, as xboard names are not always valid on ICS
5146             for(i=1; i<=36; i++) {
5147               snprintf(buf, MSG_SIZ, "wild/%d", i);
5148                 if(StringToVariant(buf) == gameInfo.variant) break;
5149             }
5150             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5151             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5152             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5153         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5154         SendToICS(ics_prefix);
5155         SendToICS(buf);
5156         if(startedFromSetupPosition || backwardMostMove != 0) {
5157           fen = PositionToFEN(backwardMostMove, NULL);
5158           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5159             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5160             SendToICS(buf);
5161           } else { // FICS: everything has to set by separate bsetup commands
5162             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5163             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5164             SendToICS(buf);
5165             if(!WhiteOnMove(backwardMostMove)) {
5166                 SendToICS("bsetup tomove black\n");
5167             }
5168             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5169             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5170             SendToICS(buf);
5171             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5172             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5173             SendToICS(buf);
5174             i = boards[backwardMostMove][EP_STATUS];
5175             if(i >= 0) { // set e.p.
5176               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5177                 SendToICS(buf);
5178             }
5179             bsetup++;
5180           }
5181         }
5182       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5183             SendToICS("bsetup done\n"); // switch to normal examining.
5184     }
5185     for(i = backwardMostMove; i<last; i++) {
5186         char buf[20];
5187         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5188         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5189             int len = strlen(moveList[i]);
5190             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5191             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5192         }
5193         SendToICS(buf);
5194     }
5195     SendToICS(ics_prefix);
5196     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5197 }
5198
5199 void
5200 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5201 {
5202     if (rf == DROP_RANK) {
5203       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5204       sprintf(move, "%c@%c%c\n",
5205                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5206     } else {
5207         if (promoChar == 'x' || promoChar == NULLCHAR) {
5208           sprintf(move, "%c%c%c%c\n",
5209                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5210         } else {
5211             sprintf(move, "%c%c%c%c%c\n",
5212                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5213         }
5214     }
5215 }
5216
5217 void
5218 ProcessICSInitScript (FILE *f)
5219 {
5220     char buf[MSG_SIZ];
5221
5222     while (fgets(buf, MSG_SIZ, f)) {
5223         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5224     }
5225
5226     fclose(f);
5227 }
5228
5229
5230 static int lastX, lastY, selectFlag, dragging;
5231
5232 void
5233 Sweep (int step)
5234 {
5235     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5236     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5237     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5238     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5239     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5240     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5241     do {
5242         promoSweep -= step;
5243         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5244         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5245         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5246         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5247         if(!step) step = -1;
5248     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5249             appData.testLegality && (promoSweep == king ||
5250             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5251     if(toX >= 0) {
5252         int victim = boards[currentMove][toY][toX];
5253         boards[currentMove][toY][toX] = promoSweep;
5254         DrawPosition(FALSE, boards[currentMove]);
5255         boards[currentMove][toY][toX] = victim;
5256     } else
5257     ChangeDragPiece(promoSweep);
5258 }
5259
5260 int
5261 PromoScroll (int x, int y)
5262 {
5263   int step = 0;
5264
5265   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5266   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5267   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5268   if(!step) return FALSE;
5269   lastX = x; lastY = y;
5270   if((promoSweep < BlackPawn) == flipView) step = -step;
5271   if(step > 0) selectFlag = 1;
5272   if(!selectFlag) Sweep(step);
5273   return FALSE;
5274 }
5275
5276 void
5277 NextPiece (int step)
5278 {
5279     ChessSquare piece = boards[currentMove][toY][toX];
5280     do {
5281         pieceSweep -= step;
5282         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5283         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5284         if(!step) step = -1;
5285     } while(PieceToChar(pieceSweep) == '.');
5286     boards[currentMove][toY][toX] = pieceSweep;
5287     DrawPosition(FALSE, boards[currentMove]);
5288     boards[currentMove][toY][toX] = piece;
5289 }
5290 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5291 void
5292 AlphaRank (char *move, int n)
5293 {
5294 //    char *p = move, c; int x, y;
5295
5296     if (appData.debugMode) {
5297         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5298     }
5299
5300     if(move[1]=='*' &&
5301        move[2]>='0' && move[2]<='9' &&
5302        move[3]>='a' && move[3]<='x'    ) {
5303         move[1] = '@';
5304         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5305         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5306     } else
5307     if(move[0]>='0' && move[0]<='9' &&
5308        move[1]>='a' && move[1]<='x' &&
5309        move[2]>='0' && move[2]<='9' &&
5310        move[3]>='a' && move[3]<='x'    ) {
5311         /* input move, Shogi -> normal */
5312         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5313         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
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[1]=='@' &&
5318        move[3]>='0' && move[3]<='9' &&
5319        move[2]>='a' && move[2]<='x'    ) {
5320         move[1] = '*';
5321         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5322         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5323     } else
5324     if(
5325        move[0]>='a' && move[0]<='x' &&
5326        move[3]>='0' && move[3]<='9' &&
5327        move[2]>='a' && move[2]<='x'    ) {
5328          /* output move, normal -> Shogi */
5329         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5330         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5331         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5332         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5333         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5334     }
5335     if (appData.debugMode) {
5336         fprintf(debugFP, "   out = '%s'\n", move);
5337     }
5338 }
5339
5340 char yy_textstr[8000];
5341
5342 /* Parser for moves from gnuchess, ICS, or user typein box */
5343 Boolean
5344 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5345 {
5346     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5347
5348     switch (*moveType) {
5349       case WhitePromotion:
5350       case BlackPromotion:
5351       case WhiteNonPromotion:
5352       case BlackNonPromotion:
5353       case NormalMove:
5354       case WhiteCapturesEnPassant:
5355       case BlackCapturesEnPassant:
5356       case WhiteKingSideCastle:
5357       case WhiteQueenSideCastle:
5358       case BlackKingSideCastle:
5359       case BlackQueenSideCastle:
5360       case WhiteKingSideCastleWild:
5361       case WhiteQueenSideCastleWild:
5362       case BlackKingSideCastleWild:
5363       case BlackQueenSideCastleWild:
5364       /* Code added by Tord: */
5365       case WhiteHSideCastleFR:
5366       case WhiteASideCastleFR:
5367       case BlackHSideCastleFR:
5368       case BlackASideCastleFR:
5369       /* End of code added by Tord */
5370       case IllegalMove:         /* bug or odd chess variant */
5371         *fromX = currentMoveString[0] - AAA;
5372         *fromY = currentMoveString[1] - ONE;
5373         *toX = currentMoveString[2] - AAA;
5374         *toY = currentMoveString[3] - ONE;
5375         *promoChar = currentMoveString[4];
5376         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5377             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5378     if (appData.debugMode) {
5379         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5380     }
5381             *fromX = *fromY = *toX = *toY = 0;
5382             return FALSE;
5383         }
5384         if (appData.testLegality) {
5385           return (*moveType != IllegalMove);
5386         } else {
5387           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5388                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5389         }
5390
5391       case WhiteDrop:
5392       case BlackDrop:
5393         *fromX = *moveType == WhiteDrop ?
5394           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5395           (int) CharToPiece(ToLower(currentMoveString[0]));
5396         *fromY = DROP_RANK;
5397         *toX = currentMoveString[2] - AAA;
5398         *toY = currentMoveString[3] - ONE;
5399         *promoChar = NULLCHAR;
5400         return TRUE;
5401
5402       case AmbiguousMove:
5403       case ImpossibleMove:
5404       case EndOfFile:
5405       case ElapsedTime:
5406       case Comment:
5407       case PGNTag:
5408       case NAG:
5409       case WhiteWins:
5410       case BlackWins:
5411       case GameIsDrawn:
5412       default:
5413     if (appData.debugMode) {
5414         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5415     }
5416         /* bug? */
5417         *fromX = *fromY = *toX = *toY = 0;
5418         *promoChar = NULLCHAR;
5419         return FALSE;
5420     }
5421 }
5422
5423 Boolean pushed = FALSE;
5424 char *lastParseAttempt;
5425
5426 void
5427 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5428 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5429   int fromX, fromY, toX, toY; char promoChar;
5430   ChessMove moveType;
5431   Boolean valid;
5432   int nr = 0;
5433
5434   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5435   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5436     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5437     pushed = TRUE;
5438   }
5439   endPV = forwardMostMove;
5440   do {
5441     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5442     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5443     lastParseAttempt = pv;
5444     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5445     if(!valid && nr == 0 &&
5446        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5447         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5448         // Hande case where played move is different from leading PV move
5449         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5450         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5451         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5452         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5453           endPV += 2; // if position different, keep this
5454           moveList[endPV-1][0] = fromX + AAA;
5455           moveList[endPV-1][1] = fromY + ONE;
5456           moveList[endPV-1][2] = toX + AAA;
5457           moveList[endPV-1][3] = toY + ONE;
5458           parseList[endPV-1][0] = NULLCHAR;
5459           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5460         }
5461       }
5462     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5463     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5464     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5465     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5466         valid++; // allow comments in PV
5467         continue;
5468     }
5469     nr++;
5470     if(endPV+1 > framePtr) break; // no space, truncate
5471     if(!valid) break;
5472     endPV++;
5473     CopyBoard(boards[endPV], boards[endPV-1]);
5474     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5475     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5476     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5477     CoordsToAlgebraic(boards[endPV - 1],
5478                              PosFlags(endPV - 1),
5479                              fromY, fromX, toY, toX, promoChar,
5480                              parseList[endPV - 1]);
5481   } while(valid);
5482   if(atEnd == 2) return; // used hidden, for PV conversion
5483   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5484   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5485   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5486                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5487   DrawPosition(TRUE, boards[currentMove]);
5488 }
5489
5490 int
5491 MultiPV (ChessProgramState *cps)
5492 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5493         int i;
5494         for(i=0; i<cps->nrOptions; i++)
5495             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5496                 return i;
5497         return -1;
5498 }
5499
5500 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5501
5502 Boolean
5503 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5504 {
5505         int startPV, multi, lineStart, origIndex = index;
5506         char *p, buf2[MSG_SIZ];
5507         ChessProgramState *cps = (pane ? &second : &first);
5508
5509         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5510         lastX = x; lastY = y;
5511         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5512         lineStart = startPV = index;
5513         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5514         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5515         index = startPV;
5516         do{ while(buf[index] && buf[index] != '\n') index++;
5517         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5518         buf[index] = 0;
5519         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5520                 int n = cps->option[multi].value;
5521                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5522                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5523                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5524                 cps->option[multi].value = n;
5525                 *start = *end = 0;
5526                 return FALSE;
5527         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5528                 ExcludeClick(origIndex - lineStart);
5529                 return FALSE;
5530         }
5531         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5532         *start = startPV; *end = index-1;
5533         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5534         return TRUE;
5535 }
5536
5537 char *
5538 PvToSAN (char *pv)
5539 {
5540         static char buf[10*MSG_SIZ];
5541         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5542         *buf = NULLCHAR;
5543         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5544         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5545         for(i = forwardMostMove; i<endPV; i++){
5546             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5547             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5548             k += strlen(buf+k);
5549         }
5550         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5551         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5552         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5553         endPV = savedEnd;
5554         return buf;
5555 }
5556
5557 Boolean
5558 LoadPV (int x, int y)
5559 { // called on right mouse click to load PV
5560   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5561   lastX = x; lastY = y;
5562   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5563   extendGame = FALSE;
5564   return TRUE;
5565 }
5566
5567 void
5568 UnLoadPV ()
5569 {
5570   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5571   if(endPV < 0) return;
5572   if(appData.autoCopyPV) CopyFENToClipboard();
5573   endPV = -1;
5574   if(extendGame && currentMove > forwardMostMove) {
5575         Boolean saveAnimate = appData.animate;
5576         if(pushed) {
5577             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5578                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5579             } else storedGames--; // abandon shelved tail of original game
5580         }
5581         pushed = FALSE;
5582         forwardMostMove = currentMove;
5583         currentMove = oldFMM;
5584         appData.animate = FALSE;
5585         ToNrEvent(forwardMostMove);
5586         appData.animate = saveAnimate;
5587   }
5588   currentMove = forwardMostMove;
5589   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5590   ClearPremoveHighlights();
5591   DrawPosition(TRUE, boards[currentMove]);
5592 }
5593
5594 void
5595 MovePV (int x, int y, int h)
5596 { // step through PV based on mouse coordinates (called on mouse move)
5597   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5598
5599   // we must somehow check if right button is still down (might be released off board!)
5600   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5601   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5602   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5603   if(!step) return;
5604   lastX = x; lastY = y;
5605
5606   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5607   if(endPV < 0) return;
5608   if(y < margin) step = 1; else
5609   if(y > h - margin) step = -1;
5610   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5611   currentMove += step;
5612   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5613   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5614                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5615   DrawPosition(FALSE, boards[currentMove]);
5616 }
5617
5618
5619 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5620 // All positions will have equal probability, but the current method will not provide a unique
5621 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5622 #define DARK 1
5623 #define LITE 2
5624 #define ANY 3
5625
5626 int squaresLeft[4];
5627 int piecesLeft[(int)BlackPawn];
5628 int seed, nrOfShuffles;
5629
5630 void
5631 GetPositionNumber ()
5632 {       // sets global variable seed
5633         int i;
5634
5635         seed = appData.defaultFrcPosition;
5636         if(seed < 0) { // randomize based on time for negative FRC position numbers
5637                 for(i=0; i<50; i++) seed += random();
5638                 seed = random() ^ random() >> 8 ^ random() << 8;
5639                 if(seed<0) seed = -seed;
5640         }
5641 }
5642
5643 int
5644 put (Board board, int pieceType, int rank, int n, int shade)
5645 // put the piece on the (n-1)-th empty squares of the given shade
5646 {
5647         int i;
5648
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5650                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5651                         board[rank][i] = (ChessSquare) pieceType;
5652                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5653                         squaresLeft[ANY]--;
5654                         piecesLeft[pieceType]--;
5655                         return i;
5656                 }
5657         }
5658         return -1;
5659 }
5660
5661
5662 void
5663 AddOnePiece (Board board, int pieceType, int rank, int shade)
5664 // calculate where the next piece goes, (any empty square), and put it there
5665 {
5666         int i;
5667
5668         i = seed % squaresLeft[shade];
5669         nrOfShuffles *= squaresLeft[shade];
5670         seed /= squaresLeft[shade];
5671         put(board, pieceType, rank, i, shade);
5672 }
5673
5674 void
5675 AddTwoPieces (Board board, int pieceType, int rank)
5676 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5677 {
5678         int i, n=squaresLeft[ANY], j=n-1, k;
5679
5680         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5681         i = seed % k;  // pick one
5682         nrOfShuffles *= k;
5683         seed /= k;
5684         while(i >= j) i -= j--;
5685         j = n - 1 - j; i += j;
5686         put(board, pieceType, rank, j, ANY);
5687         put(board, pieceType, rank, i, ANY);
5688 }
5689
5690 void
5691 SetUpShuffle (Board board, int number)
5692 {
5693         int i, p, first=1;
5694
5695         GetPositionNumber(); nrOfShuffles = 1;
5696
5697         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5698         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5699         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5700
5701         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5702
5703         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5704             p = (int) board[0][i];
5705             if(p < (int) BlackPawn) piecesLeft[p] ++;
5706             board[0][i] = EmptySquare;
5707         }
5708
5709         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5710             // shuffles restricted to allow normal castling put KRR first
5711             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5712                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5713             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5714                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5715             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5716                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5717             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5718                 put(board, WhiteRook, 0, 0, ANY);
5719             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5720         }
5721
5722         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5723             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5724             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5725                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5726                 while(piecesLeft[p] >= 2) {
5727                     AddOnePiece(board, p, 0, LITE);
5728                     AddOnePiece(board, p, 0, DARK);
5729                 }
5730                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5731             }
5732
5733         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5734             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5735             // but we leave King and Rooks for last, to possibly obey FRC restriction
5736             if(p == (int)WhiteRook) continue;
5737             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5738             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5739         }
5740
5741         // now everything is placed, except perhaps King (Unicorn) and Rooks
5742
5743         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5744             // Last King gets castling rights
5745             while(piecesLeft[(int)WhiteUnicorn]) {
5746                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5747                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5748             }
5749
5750             while(piecesLeft[(int)WhiteKing]) {
5751                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5752                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5753             }
5754
5755
5756         } else {
5757             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5758             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5759         }
5760
5761         // Only Rooks can be left; simply place them all
5762         while(piecesLeft[(int)WhiteRook]) {
5763                 i = put(board, WhiteRook, 0, 0, ANY);
5764                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5765                         if(first) {
5766                                 first=0;
5767                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5768                         }
5769                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5770                 }
5771         }
5772         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5773             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5774         }
5775
5776         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5777 }
5778
5779 int
5780 SetCharTable (char *table, const char * map)
5781 /* [HGM] moved here from winboard.c because of its general usefulness */
5782 /*       Basically a safe strcpy that uses the last character as King */
5783 {
5784     int result = FALSE; int NrPieces;
5785
5786     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5787                     && NrPieces >= 12 && !(NrPieces&1)) {
5788         int i; /* [HGM] Accept even length from 12 to 34 */
5789
5790         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5791         for( i=0; i<NrPieces/2-1; i++ ) {
5792             table[i] = map[i];
5793             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5794         }
5795         table[(int) WhiteKing]  = map[NrPieces/2-1];
5796         table[(int) BlackKing]  = map[NrPieces-1];
5797
5798         result = TRUE;
5799     }
5800
5801     return result;
5802 }
5803
5804 void
5805 Prelude (Board board)
5806 {       // [HGM] superchess: random selection of exo-pieces
5807         int i, j, k; ChessSquare p;
5808         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5809
5810         GetPositionNumber(); // use FRC position number
5811
5812         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5813             SetCharTable(pieceToChar, appData.pieceToCharTable);
5814             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5815                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5816         }
5817
5818         j = seed%4;                 seed /= 4;
5819         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5820         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5821         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5822         j = seed%3 + (seed%3 >= j); seed /= 3;
5823         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5824         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5825         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5826         j = seed%3;                 seed /= 3;
5827         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5828         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5829         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5830         j = seed%2 + (seed%2 >= j); seed /= 2;
5831         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5832         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5833         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5834         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5835         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5836         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5837         put(board, exoPieces[0],    0, 0, ANY);
5838         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5839 }
5840
5841 void
5842 InitPosition (int redraw)
5843 {
5844     ChessSquare (* pieces)[BOARD_FILES];
5845     int i, j, pawnRow, overrule,
5846     oldx = gameInfo.boardWidth,
5847     oldy = gameInfo.boardHeight,
5848     oldh = gameInfo.holdingsWidth;
5849     static int oldv;
5850
5851     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5852
5853     /* [AS] Initialize pv info list [HGM] and game status */
5854     {
5855         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5856             pvInfoList[i].depth = 0;
5857             boards[i][EP_STATUS] = EP_NONE;
5858             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5859         }
5860
5861         initialRulePlies = 0; /* 50-move counter start */
5862
5863         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5864         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5865     }
5866
5867
5868     /* [HGM] logic here is completely changed. In stead of full positions */
5869     /* the initialized data only consist of the two backranks. The switch */
5870     /* selects which one we will use, which is than copied to the Board   */
5871     /* initialPosition, which for the rest is initialized by Pawns and    */
5872     /* empty squares. This initial position is then copied to boards[0],  */
5873     /* possibly after shuffling, so that it remains available.            */
5874
5875     gameInfo.holdingsWidth = 0; /* default board sizes */
5876     gameInfo.boardWidth    = 8;
5877     gameInfo.boardHeight   = 8;
5878     gameInfo.holdingsSize  = 0;
5879     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5880     for(i=0; i<BOARD_FILES-2; i++)
5881       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5882     initialPosition[EP_STATUS] = EP_NONE;
5883     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5884     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5885          SetCharTable(pieceNickName, appData.pieceNickNames);
5886     else SetCharTable(pieceNickName, "............");
5887     pieces = FIDEArray;
5888
5889     switch (gameInfo.variant) {
5890     case VariantFischeRandom:
5891       shuffleOpenings = TRUE;
5892     default:
5893       break;
5894     case VariantShatranj:
5895       pieces = ShatranjArray;
5896       nrCastlingRights = 0;
5897       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5898       break;
5899     case VariantMakruk:
5900       pieces = makrukArray;
5901       nrCastlingRights = 0;
5902       startedFromSetupPosition = TRUE;
5903       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5904       break;
5905     case VariantTwoKings:
5906       pieces = twoKingsArray;
5907       break;
5908     case VariantGrand:
5909       pieces = GrandArray;
5910       nrCastlingRights = 0;
5911       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5912       gameInfo.boardWidth = 10;
5913       gameInfo.boardHeight = 10;
5914       gameInfo.holdingsSize = 7;
5915       break;
5916     case VariantCapaRandom:
5917       shuffleOpenings = TRUE;
5918     case VariantCapablanca:
5919       pieces = CapablancaArray;
5920       gameInfo.boardWidth = 10;
5921       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5922       break;
5923     case VariantGothic:
5924       pieces = GothicArray;
5925       gameInfo.boardWidth = 10;
5926       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5927       break;
5928     case VariantSChess:
5929       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5930       gameInfo.holdingsSize = 7;
5931       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5932       break;
5933     case VariantJanus:
5934       pieces = JanusArray;
5935       gameInfo.boardWidth = 10;
5936       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5937       nrCastlingRights = 6;
5938         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5939         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5940         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5941         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5942         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5943         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5944       break;
5945     case VariantFalcon:
5946       pieces = FalconArray;
5947       gameInfo.boardWidth = 10;
5948       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5949       break;
5950     case VariantXiangqi:
5951       pieces = XiangqiArray;
5952       gameInfo.boardWidth  = 9;
5953       gameInfo.boardHeight = 10;
5954       nrCastlingRights = 0;
5955       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5956       break;
5957     case VariantShogi:
5958       pieces = ShogiArray;
5959       gameInfo.boardWidth  = 9;
5960       gameInfo.boardHeight = 9;
5961       gameInfo.holdingsSize = 7;
5962       nrCastlingRights = 0;
5963       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5964       break;
5965     case VariantCourier:
5966       pieces = CourierArray;
5967       gameInfo.boardWidth  = 12;
5968       nrCastlingRights = 0;
5969       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5970       break;
5971     case VariantKnightmate:
5972       pieces = KnightmateArray;
5973       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5974       break;
5975     case VariantSpartan:
5976       pieces = SpartanArray;
5977       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5978       break;
5979     case VariantFairy:
5980       pieces = fairyArray;
5981       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5982       break;
5983     case VariantGreat:
5984       pieces = GreatArray;
5985       gameInfo.boardWidth = 10;
5986       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5987       gameInfo.holdingsSize = 8;
5988       break;
5989     case VariantSuper:
5990       pieces = FIDEArray;
5991       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5992       gameInfo.holdingsSize = 8;
5993       startedFromSetupPosition = TRUE;
5994       break;
5995     case VariantCrazyhouse:
5996     case VariantBughouse:
5997       pieces = FIDEArray;
5998       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5999       gameInfo.holdingsSize = 5;
6000       break;
6001     case VariantWildCastle:
6002       pieces = FIDEArray;
6003       /* !!?shuffle with kings guaranteed to be on d or e file */
6004       shuffleOpenings = 1;
6005       break;
6006     case VariantNoCastle:
6007       pieces = FIDEArray;
6008       nrCastlingRights = 0;
6009       /* !!?unconstrained back-rank shuffle */
6010       shuffleOpenings = 1;
6011       break;
6012     }
6013
6014     overrule = 0;
6015     if(appData.NrFiles >= 0) {
6016         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6017         gameInfo.boardWidth = appData.NrFiles;
6018     }
6019     if(appData.NrRanks >= 0) {
6020         gameInfo.boardHeight = appData.NrRanks;
6021     }
6022     if(appData.holdingsSize >= 0) {
6023         i = appData.holdingsSize;
6024         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6025         gameInfo.holdingsSize = i;
6026     }
6027     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6028     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6029         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6030
6031     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6032     if(pawnRow < 1) pawnRow = 1;
6033     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6034
6035     /* User pieceToChar list overrules defaults */
6036     if(appData.pieceToCharTable != NULL)
6037         SetCharTable(pieceToChar, appData.pieceToCharTable);
6038
6039     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6040
6041         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6042             s = (ChessSquare) 0; /* account holding counts in guard band */
6043         for( i=0; i<BOARD_HEIGHT; i++ )
6044             initialPosition[i][j] = s;
6045
6046         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6047         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6048         initialPosition[pawnRow][j] = WhitePawn;
6049         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6050         if(gameInfo.variant == VariantXiangqi) {
6051             if(j&1) {
6052                 initialPosition[pawnRow][j] =
6053                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6054                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6055                    initialPosition[2][j] = WhiteCannon;
6056                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6057                 }
6058             }
6059         }
6060         if(gameInfo.variant == VariantGrand) {
6061             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6062                initialPosition[0][j] = WhiteRook;
6063                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6064             }
6065         }
6066         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6067     }
6068     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6069
6070             j=BOARD_LEFT+1;
6071             initialPosition[1][j] = WhiteBishop;
6072             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6073             j=BOARD_RGHT-2;
6074             initialPosition[1][j] = WhiteRook;
6075             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6076     }
6077
6078     if( nrCastlingRights == -1) {
6079         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6080         /*       This sets default castling rights from none to normal corners   */
6081         /* Variants with other castling rights must set them themselves above    */
6082         nrCastlingRights = 6;
6083
6084         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6085         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6086         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6087         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6088         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6089         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6090      }
6091
6092      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6093      if(gameInfo.variant == VariantGreat) { // promotion commoners
6094         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6095         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6096         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6097         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6098      }
6099      if( gameInfo.variant == VariantSChess ) {
6100       initialPosition[1][0] = BlackMarshall;
6101       initialPosition[2][0] = BlackAngel;
6102       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6103       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6104       initialPosition[1][1] = initialPosition[2][1] =
6105       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6106      }
6107   if (appData.debugMode) {
6108     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6109   }
6110     if(shuffleOpenings) {
6111         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6112         startedFromSetupPosition = TRUE;
6113     }
6114     if(startedFromPositionFile) {
6115       /* [HGM] loadPos: use PositionFile for every new game */
6116       CopyBoard(initialPosition, filePosition);
6117       for(i=0; i<nrCastlingRights; i++)
6118           initialRights[i] = filePosition[CASTLING][i];
6119       startedFromSetupPosition = TRUE;
6120     }
6121
6122     CopyBoard(boards[0], initialPosition);
6123
6124     if(oldx != gameInfo.boardWidth ||
6125        oldy != gameInfo.boardHeight ||
6126        oldv != gameInfo.variant ||
6127        oldh != gameInfo.holdingsWidth
6128                                          )
6129             InitDrawingSizes(-2 ,0);
6130
6131     oldv = gameInfo.variant;
6132     if (redraw)
6133       DrawPosition(TRUE, boards[currentMove]);
6134 }
6135
6136 void
6137 SendBoard (ChessProgramState *cps, int moveNum)
6138 {
6139     char message[MSG_SIZ];
6140
6141     if (cps->useSetboard) {
6142       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6143       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6144       SendToProgram(message, cps);
6145       free(fen);
6146
6147     } else {
6148       ChessSquare *bp;
6149       int i, j, left=0, right=BOARD_WIDTH;
6150       /* Kludge to set black to move, avoiding the troublesome and now
6151        * deprecated "black" command.
6152        */
6153       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6154         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6155
6156       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6157
6158       SendToProgram("edit\n", cps);
6159       SendToProgram("#\n", cps);
6160       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6161         bp = &boards[moveNum][i][left];
6162         for (j = left; j < right; j++, bp++) {
6163           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6164           if ((int) *bp < (int) BlackPawn) {
6165             if(j == BOARD_RGHT+1)
6166                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6167             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6168             if(message[0] == '+' || message[0] == '~') {
6169               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6170                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6171                         AAA + j, ONE + i);
6172             }
6173             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6174                 message[1] = BOARD_RGHT   - 1 - j + '1';
6175                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6176             }
6177             SendToProgram(message, cps);
6178           }
6179         }
6180       }
6181
6182       SendToProgram("c\n", cps);
6183       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6184         bp = &boards[moveNum][i][left];
6185         for (j = left; j < right; j++, bp++) {
6186           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6187           if (((int) *bp != (int) EmptySquare)
6188               && ((int) *bp >= (int) BlackPawn)) {
6189             if(j == BOARD_LEFT-2)
6190                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6191             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6192                     AAA + j, ONE + i);
6193             if(message[0] == '+' || message[0] == '~') {
6194               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6195                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6196                         AAA + j, ONE + i);
6197             }
6198             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6199                 message[1] = BOARD_RGHT   - 1 - j + '1';
6200                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6201             }
6202             SendToProgram(message, cps);
6203           }
6204         }
6205       }
6206
6207       SendToProgram(".\n", cps);
6208     }
6209     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6210 }
6211
6212 char exclusionHeader[MSG_SIZ];
6213 int exCnt, excludePtr;
6214 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6215 static Exclusion excluTab[200];
6216 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6217
6218 static void
6219 WriteMap (int s)
6220 {
6221     int j;
6222     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6223     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6224 }
6225
6226 static void
6227 ClearMap ()
6228 {
6229     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6230     excludePtr = 24; exCnt = 0;
6231     WriteMap(0);
6232 }
6233
6234 static void
6235 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6236 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6237     char buf[2*MOVE_LEN], *p;
6238     Exclusion *e = excluTab;
6239     int i;
6240     for(i=0; i<exCnt; i++)
6241         if(e[i].ff == fromX && e[i].fr == fromY &&
6242            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6243     if(i == exCnt) { // was not in exclude list; add it
6244         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6245         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6246             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6247             return; // abort
6248         }
6249         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6250         excludePtr++; e[i].mark = excludePtr++;
6251         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6252         exCnt++;
6253     }
6254     exclusionHeader[e[i].mark] = state;
6255 }
6256
6257 static int
6258 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6259 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6260     char buf[MSG_SIZ];
6261     int j, k;
6262     ChessMove moveType;
6263     if((signed char)promoChar == -1) { // kludge to indicate best move
6264         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6265             return 1; // if unparsable, abort
6266     }
6267     // update exclusion map (resolving toggle by consulting existing state)
6268     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6269     j = k%8; k >>= 3;
6270     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6271     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6272          excludeMap[k] |=   1<<j;
6273     else excludeMap[k] &= ~(1<<j);
6274     // update header
6275     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6276     // inform engine
6277     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6278     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6279     SendToBoth(buf);
6280     return (state == '+');
6281 }
6282
6283 static void
6284 ExcludeClick (int index)
6285 {
6286     int i, j;
6287     Exclusion *e = excluTab;
6288     if(index < 25) { // none, best or tail clicked
6289         if(index < 13) { // none: include all
6290             WriteMap(0); // clear map
6291             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6292             SendToBoth("include all\n"); // and inform engine
6293         } else if(index > 18) { // tail
6294             if(exclusionHeader[19] == '-') { // tail was excluded
6295                 SendToBoth("include all\n");
6296                 WriteMap(0); // clear map completely
6297                 // now re-exclude selected moves
6298                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6299                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6300             } else { // tail was included or in mixed state
6301                 SendToBoth("exclude all\n");
6302                 WriteMap(0xFF); // fill map completely
6303                 // now re-include selected moves
6304                 j = 0; // count them
6305                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6306                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6307                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6308             }
6309         } else { // best
6310             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6311         }
6312     } else {
6313         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6314             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6315             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6316             break;
6317         }
6318     }
6319 }
6320
6321 ChessSquare
6322 DefaultPromoChoice (int white)
6323 {
6324     ChessSquare result;
6325     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6326         result = WhiteFerz; // no choice
6327     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6328         result= WhiteKing; // in Suicide Q is the last thing we want
6329     else if(gameInfo.variant == VariantSpartan)
6330         result = white ? WhiteQueen : WhiteAngel;
6331     else result = WhiteQueen;
6332     if(!white) result = WHITE_TO_BLACK result;
6333     return result;
6334 }
6335
6336 static int autoQueen; // [HGM] oneclick
6337
6338 int
6339 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6340 {
6341     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6342     /* [HGM] add Shogi promotions */
6343     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6344     ChessSquare piece;
6345     ChessMove moveType;
6346     Boolean premove;
6347
6348     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6349     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6350
6351     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6352       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6353         return FALSE;
6354
6355     piece = boards[currentMove][fromY][fromX];
6356     if(gameInfo.variant == VariantShogi) {
6357         promotionZoneSize = BOARD_HEIGHT/3;
6358         highestPromotingPiece = (int)WhiteFerz;
6359     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6360         promotionZoneSize = 3;
6361     }
6362
6363     // Treat Lance as Pawn when it is not representing Amazon
6364     if(gameInfo.variant != VariantSuper) {
6365         if(piece == WhiteLance) piece = WhitePawn; else
6366         if(piece == BlackLance) piece = BlackPawn;
6367     }
6368
6369     // next weed out all moves that do not touch the promotion zone at all
6370     if((int)piece >= BlackPawn) {
6371         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6372              return FALSE;
6373         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6374     } else {
6375         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6376            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6377     }
6378
6379     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6380
6381     // weed out mandatory Shogi promotions
6382     if(gameInfo.variant == VariantShogi) {
6383         if(piece >= BlackPawn) {
6384             if(toY == 0 && piece == BlackPawn ||
6385                toY == 0 && piece == BlackQueen ||
6386                toY <= 1 && piece == BlackKnight) {
6387                 *promoChoice = '+';
6388                 return FALSE;
6389             }
6390         } else {
6391             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6392                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6393                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6394                 *promoChoice = '+';
6395                 return FALSE;
6396             }
6397         }
6398     }
6399
6400     // weed out obviously illegal Pawn moves
6401     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6402         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6403         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6404         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6405         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6406         // note we are not allowed to test for valid (non-)capture, due to premove
6407     }
6408
6409     // we either have a choice what to promote to, or (in Shogi) whether to promote
6410     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6411         *promoChoice = PieceToChar(BlackFerz);  // no choice
6412         return FALSE;
6413     }
6414     // no sense asking what we must promote to if it is going to explode...
6415     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6416         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6417         return FALSE;
6418     }
6419     // give caller the default choice even if we will not make it
6420     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6421     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6422     if(        sweepSelect && gameInfo.variant != VariantGreat
6423                            && gameInfo.variant != VariantGrand
6424                            && gameInfo.variant != VariantSuper) return FALSE;
6425     if(autoQueen) return FALSE; // predetermined
6426
6427     // suppress promotion popup on illegal moves that are not premoves
6428     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6429               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6430     if(appData.testLegality && !premove) {
6431         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6432                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6433         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6434             return FALSE;
6435     }
6436
6437     return TRUE;
6438 }
6439
6440 int
6441 InPalace (int row, int column)
6442 {   /* [HGM] for Xiangqi */
6443     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6444          column < (BOARD_WIDTH + 4)/2 &&
6445          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6446     return FALSE;
6447 }
6448
6449 int
6450 PieceForSquare (int x, int y)
6451 {
6452   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6453      return -1;
6454   else
6455      return boards[currentMove][y][x];
6456 }
6457
6458 int
6459 OKToStartUserMove (int x, int y)
6460 {
6461     ChessSquare from_piece;
6462     int white_piece;
6463
6464     if (matchMode) return FALSE;
6465     if (gameMode == EditPosition) return TRUE;
6466
6467     if (x >= 0 && y >= 0)
6468       from_piece = boards[currentMove][y][x];
6469     else
6470       from_piece = EmptySquare;
6471
6472     if (from_piece == EmptySquare) return FALSE;
6473
6474     white_piece = (int)from_piece >= (int)WhitePawn &&
6475       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6476
6477     switch (gameMode) {
6478       case AnalyzeFile:
6479       case TwoMachinesPlay:
6480       case EndOfGame:
6481         return FALSE;
6482
6483       case IcsObserving:
6484       case IcsIdle:
6485         return FALSE;
6486
6487       case MachinePlaysWhite:
6488       case IcsPlayingBlack:
6489         if (appData.zippyPlay) return FALSE;
6490         if (white_piece) {
6491             DisplayMoveError(_("You are playing Black"));
6492             return FALSE;
6493         }
6494         break;
6495
6496       case MachinePlaysBlack:
6497       case IcsPlayingWhite:
6498         if (appData.zippyPlay) return FALSE;
6499         if (!white_piece) {
6500             DisplayMoveError(_("You are playing White"));
6501             return FALSE;
6502         }
6503         break;
6504
6505       case PlayFromGameFile:
6506             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6507       case EditGame:
6508         if (!white_piece && WhiteOnMove(currentMove)) {
6509             DisplayMoveError(_("It is White's turn"));
6510             return FALSE;
6511         }
6512         if (white_piece && !WhiteOnMove(currentMove)) {
6513             DisplayMoveError(_("It is Black's turn"));
6514             return FALSE;
6515         }
6516         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6517             /* Editing correspondence game history */
6518             /* Could disallow this or prompt for confirmation */
6519             cmailOldMove = -1;
6520         }
6521         break;
6522
6523       case BeginningOfGame:
6524         if (appData.icsActive) return FALSE;
6525         if (!appData.noChessProgram) {
6526             if (!white_piece) {
6527                 DisplayMoveError(_("You are playing White"));
6528                 return FALSE;
6529             }
6530         }
6531         break;
6532
6533       case Training:
6534         if (!white_piece && WhiteOnMove(currentMove)) {
6535             DisplayMoveError(_("It is White's turn"));
6536             return FALSE;
6537         }
6538         if (white_piece && !WhiteOnMove(currentMove)) {
6539             DisplayMoveError(_("It is Black's turn"));
6540             return FALSE;
6541         }
6542         break;
6543
6544       default:
6545       case IcsExamining:
6546         break;
6547     }
6548     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6549         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6550         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6551         && gameMode != AnalyzeFile && gameMode != Training) {
6552         DisplayMoveError(_("Displayed position is not current"));
6553         return FALSE;
6554     }
6555     return TRUE;
6556 }
6557
6558 Boolean
6559 OnlyMove (int *x, int *y, Boolean captures)
6560 {
6561     DisambiguateClosure cl;
6562     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6563     switch(gameMode) {
6564       case MachinePlaysBlack:
6565       case IcsPlayingWhite:
6566       case BeginningOfGame:
6567         if(!WhiteOnMove(currentMove)) return FALSE;
6568         break;
6569       case MachinePlaysWhite:
6570       case IcsPlayingBlack:
6571         if(WhiteOnMove(currentMove)) return FALSE;
6572         break;
6573       case EditGame:
6574         break;
6575       default:
6576         return FALSE;
6577     }
6578     cl.pieceIn = EmptySquare;
6579     cl.rfIn = *y;
6580     cl.ffIn = *x;
6581     cl.rtIn = -1;
6582     cl.ftIn = -1;
6583     cl.promoCharIn = NULLCHAR;
6584     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6585     if( cl.kind == NormalMove ||
6586         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6587         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6588         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6589       fromX = cl.ff;
6590       fromY = cl.rf;
6591       *x = cl.ft;
6592       *y = cl.rt;
6593       return TRUE;
6594     }
6595     if(cl.kind != ImpossibleMove) return FALSE;
6596     cl.pieceIn = EmptySquare;
6597     cl.rfIn = -1;
6598     cl.ffIn = -1;
6599     cl.rtIn = *y;
6600     cl.ftIn = *x;
6601     cl.promoCharIn = NULLCHAR;
6602     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6603     if( cl.kind == NormalMove ||
6604         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6605         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6606         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6607       fromX = cl.ff;
6608       fromY = cl.rf;
6609       *x = cl.ft;
6610       *y = cl.rt;
6611       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6612       return TRUE;
6613     }
6614     return FALSE;
6615 }
6616
6617 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6618 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6619 int lastLoadGameUseList = FALSE;
6620 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6621 ChessMove lastLoadGameStart = EndOfFile;
6622 int doubleClick;
6623
6624 void
6625 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6626 {
6627     ChessMove moveType;
6628     ChessSquare pup;
6629     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6630
6631     /* Check if the user is playing in turn.  This is complicated because we
6632        let the user "pick up" a piece before it is his turn.  So the piece he
6633        tried to pick up may have been captured by the time he puts it down!
6634        Therefore we use the color the user is supposed to be playing in this
6635        test, not the color of the piece that is currently on the starting
6636        square---except in EditGame mode, where the user is playing both
6637        sides; fortunately there the capture race can't happen.  (It can
6638        now happen in IcsExamining mode, but that's just too bad.  The user
6639        will get a somewhat confusing message in that case.)
6640        */
6641
6642     switch (gameMode) {
6643       case AnalyzeFile:
6644       case TwoMachinesPlay:
6645       case EndOfGame:
6646       case IcsObserving:
6647       case IcsIdle:
6648         /* We switched into a game mode where moves are not accepted,
6649            perhaps while the mouse button was down. */
6650         return;
6651
6652       case MachinePlaysWhite:
6653         /* User is moving for Black */
6654         if (WhiteOnMove(currentMove)) {
6655             DisplayMoveError(_("It is White's turn"));
6656             return;
6657         }
6658         break;
6659
6660       case MachinePlaysBlack:
6661         /* User is moving for White */
6662         if (!WhiteOnMove(currentMove)) {
6663             DisplayMoveError(_("It is Black's turn"));
6664             return;
6665         }
6666         break;
6667
6668       case PlayFromGameFile:
6669             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6670       case EditGame:
6671       case IcsExamining:
6672       case BeginningOfGame:
6673       case AnalyzeMode:
6674       case Training:
6675         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6676         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6677             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6678             /* User is moving for Black */
6679             if (WhiteOnMove(currentMove)) {
6680                 DisplayMoveError(_("It is White's turn"));
6681                 return;
6682             }
6683         } else {
6684             /* User is moving for White */
6685             if (!WhiteOnMove(currentMove)) {
6686                 DisplayMoveError(_("It is Black's turn"));
6687                 return;
6688             }
6689         }
6690         break;
6691
6692       case IcsPlayingBlack:
6693         /* User is moving for Black */
6694         if (WhiteOnMove(currentMove)) {
6695             if (!appData.premove) {
6696                 DisplayMoveError(_("It is White's turn"));
6697             } else if (toX >= 0 && toY >= 0) {
6698                 premoveToX = toX;
6699                 premoveToY = toY;
6700                 premoveFromX = fromX;
6701                 premoveFromY = fromY;
6702                 premovePromoChar = promoChar;
6703                 gotPremove = 1;
6704                 if (appData.debugMode)
6705                     fprintf(debugFP, "Got premove: fromX %d,"
6706                             "fromY %d, toX %d, toY %d\n",
6707                             fromX, fromY, toX, toY);
6708             }
6709             return;
6710         }
6711         break;
6712
6713       case IcsPlayingWhite:
6714         /* User is moving for White */
6715         if (!WhiteOnMove(currentMove)) {
6716             if (!appData.premove) {
6717                 DisplayMoveError(_("It is Black's turn"));
6718             } else if (toX >= 0 && toY >= 0) {
6719                 premoveToX = toX;
6720                 premoveToY = toY;
6721                 premoveFromX = fromX;
6722                 premoveFromY = fromY;
6723                 premovePromoChar = promoChar;
6724                 gotPremove = 1;
6725                 if (appData.debugMode)
6726                     fprintf(debugFP, "Got premove: fromX %d,"
6727                             "fromY %d, toX %d, toY %d\n",
6728                             fromX, fromY, toX, toY);
6729             }
6730             return;
6731         }
6732         break;
6733
6734       default:
6735         break;
6736
6737       case EditPosition:
6738         /* EditPosition, empty square, or different color piece;
6739            click-click move is possible */
6740         if (toX == -2 || toY == -2) {
6741             boards[0][fromY][fromX] = EmptySquare;
6742             DrawPosition(FALSE, boards[currentMove]);
6743             return;
6744         } else if (toX >= 0 && toY >= 0) {
6745             boards[0][toY][toX] = boards[0][fromY][fromX];
6746             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6747                 if(boards[0][fromY][0] != EmptySquare) {
6748                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6749                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6750                 }
6751             } else
6752             if(fromX == BOARD_RGHT+1) {
6753                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6754                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6755                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6756                 }
6757             } else
6758             boards[0][fromY][fromX] = gatingPiece;
6759             DrawPosition(FALSE, boards[currentMove]);
6760             return;
6761         }
6762         return;
6763     }
6764
6765     if(toX < 0 || toY < 0) return;
6766     pup = boards[currentMove][toY][toX];
6767
6768     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6769     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6770          if( pup != EmptySquare ) return;
6771          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6772            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6773                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6774            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6775            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6776            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6777            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6778          fromY = DROP_RANK;
6779     }
6780
6781     /* [HGM] always test for legality, to get promotion info */
6782     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6783                                          fromY, fromX, toY, toX, promoChar);
6784
6785     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6786
6787     /* [HGM] but possibly ignore an IllegalMove result */
6788     if (appData.testLegality) {
6789         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6790             DisplayMoveError(_("Illegal move"));
6791             return;
6792         }
6793     }
6794
6795     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6796         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6797              ClearPremoveHighlights(); // was included
6798         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6799         return;
6800     }
6801
6802     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6803 }
6804
6805 /* Common tail of UserMoveEvent and DropMenuEvent */
6806 int
6807 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6808 {
6809     char *bookHit = 0;
6810
6811     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6812         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6813         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6814         if(WhiteOnMove(currentMove)) {
6815             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6816         } else {
6817             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6818         }
6819     }
6820
6821     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6822        move type in caller when we know the move is a legal promotion */
6823     if(moveType == NormalMove && promoChar)
6824         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6825
6826     /* [HGM] <popupFix> The following if has been moved here from
6827        UserMoveEvent(). Because it seemed to belong here (why not allow
6828        piece drops in training games?), and because it can only be
6829        performed after it is known to what we promote. */
6830     if (gameMode == Training) {
6831       /* compare the move played on the board to the next move in the
6832        * game. If they match, display the move and the opponent's response.
6833        * If they don't match, display an error message.
6834        */
6835       int saveAnimate;
6836       Board testBoard;
6837       CopyBoard(testBoard, boards[currentMove]);
6838       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6839
6840       if (CompareBoards(testBoard, boards[currentMove+1])) {
6841         ForwardInner(currentMove+1);
6842
6843         /* Autoplay the opponent's response.
6844          * if appData.animate was TRUE when Training mode was entered,
6845          * the response will be animated.
6846          */
6847         saveAnimate = appData.animate;
6848         appData.animate = animateTraining;
6849         ForwardInner(currentMove+1);
6850         appData.animate = saveAnimate;
6851
6852         /* check for the end of the game */
6853         if (currentMove >= forwardMostMove) {
6854           gameMode = PlayFromGameFile;
6855           ModeHighlight();
6856           SetTrainingModeOff();
6857           DisplayInformation(_("End of game"));
6858         }
6859       } else {
6860         DisplayError(_("Incorrect move"), 0);
6861       }
6862       return 1;
6863     }
6864
6865   /* Ok, now we know that the move is good, so we can kill
6866      the previous line in Analysis Mode */
6867   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6868                                 && currentMove < forwardMostMove) {
6869     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6870     else forwardMostMove = currentMove;
6871   }
6872
6873   ClearMap();
6874
6875   /* If we need the chess program but it's dead, restart it */
6876   ResurrectChessProgram();
6877
6878   /* A user move restarts a paused game*/
6879   if (pausing)
6880     PauseEvent();
6881
6882   thinkOutput[0] = NULLCHAR;
6883
6884   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6885
6886   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6887     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6888     return 1;
6889   }
6890
6891   if (gameMode == BeginningOfGame) {
6892     if (appData.noChessProgram) {
6893       gameMode = EditGame;
6894       SetGameInfo();
6895     } else {
6896       char buf[MSG_SIZ];
6897       gameMode = MachinePlaysBlack;
6898       StartClocks();
6899       SetGameInfo();
6900       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6901       DisplayTitle(buf);
6902       if (first.sendName) {
6903         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6904         SendToProgram(buf, &first);
6905       }
6906       StartClocks();
6907     }
6908     ModeHighlight();
6909   }
6910
6911   /* Relay move to ICS or chess engine */
6912   if (appData.icsActive) {
6913     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6914         gameMode == IcsExamining) {
6915       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6916         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6917         SendToICS("draw ");
6918         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6919       }
6920       // also send plain move, in case ICS does not understand atomic claims
6921       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6922       ics_user_moved = 1;
6923     }
6924   } else {
6925     if (first.sendTime && (gameMode == BeginningOfGame ||
6926                            gameMode == MachinePlaysWhite ||
6927                            gameMode == MachinePlaysBlack)) {
6928       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6929     }
6930     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6931          // [HGM] book: if program might be playing, let it use book
6932         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6933         first.maybeThinking = TRUE;
6934     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6935         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6936         SendBoard(&first, currentMove+1);
6937         if(second.analyzing) {
6938             if(!second.useSetboard) SendToProgram("undo\n", &second);
6939             SendBoard(&second, currentMove+1);
6940         }
6941     } else {
6942         SendMoveToProgram(forwardMostMove-1, &first);
6943         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6944     }
6945     if (currentMove == cmailOldMove + 1) {
6946       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6947     }
6948   }
6949
6950   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6951
6952   switch (gameMode) {
6953   case EditGame:
6954     if(appData.testLegality)
6955     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6956     case MT_NONE:
6957     case MT_CHECK:
6958       break;
6959     case MT_CHECKMATE:
6960     case MT_STAINMATE:
6961       if (WhiteOnMove(currentMove)) {
6962         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6963       } else {
6964         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6965       }
6966       break;
6967     case MT_STALEMATE:
6968       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6969       break;
6970     }
6971     break;
6972
6973   case MachinePlaysBlack:
6974   case MachinePlaysWhite:
6975     /* disable certain menu options while machine is thinking */
6976     SetMachineThinkingEnables();
6977     break;
6978
6979   default:
6980     break;
6981   }
6982
6983   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6984   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6985
6986   if(bookHit) { // [HGM] book: simulate book reply
6987         static char bookMove[MSG_SIZ]; // a bit generous?
6988
6989         programStats.nodes = programStats.depth = programStats.time =
6990         programStats.score = programStats.got_only_move = 0;
6991         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6992
6993         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6994         strcat(bookMove, bookHit);
6995         HandleMachineMove(bookMove, &first);
6996   }
6997   return 1;
6998 }
6999
7000 void
7001 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7002 {
7003     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7004     Markers *m = (Markers *) closure;
7005     if(rf == fromY && ff == fromX)
7006         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7007                          || kind == WhiteCapturesEnPassant
7008                          || kind == BlackCapturesEnPassant);
7009     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7010 }
7011
7012 void
7013 MarkTargetSquares (int clear)
7014 {
7015   int x, y;
7016   if(clear) // no reason to ever suppress clearing
7017     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7018   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7019      !appData.testLegality || gameMode == EditPosition) return;
7020   if(!clear) {
7021     int capt = 0;
7022     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7023     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7024       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7025       if(capt)
7026       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7027     }
7028   }
7029   DrawPosition(FALSE, NULL);
7030 }
7031
7032 int
7033 Explode (Board board, int fromX, int fromY, int toX, int toY)
7034 {
7035     if(gameInfo.variant == VariantAtomic &&
7036        (board[toY][toX] != EmptySquare ||                     // capture?
7037         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7038                          board[fromY][fromX] == BlackPawn   )
7039       )) {
7040         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7041         return TRUE;
7042     }
7043     return FALSE;
7044 }
7045
7046 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7047
7048 int
7049 CanPromote (ChessSquare piece, int y)
7050 {
7051         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7052         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7053         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7054            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7055            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7056                                                   gameInfo.variant == VariantMakruk) return FALSE;
7057         return (piece == BlackPawn && y == 1 ||
7058                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7059                 piece == BlackLance && y == 1 ||
7060                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7061 }
7062
7063 void
7064 LeftClick (ClickType clickType, int xPix, int yPix)
7065 {
7066     int x, y;
7067     Boolean saveAnimate;
7068     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7069     char promoChoice = NULLCHAR;
7070     ChessSquare piece;
7071     static TimeMark lastClickTime, prevClickTime;
7072
7073     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7074
7075     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7076
7077     if (clickType == Press) ErrorPopDown();
7078
7079     x = EventToSquare(xPix, BOARD_WIDTH);
7080     y = EventToSquare(yPix, BOARD_HEIGHT);
7081     if (!flipView && y >= 0) {
7082         y = BOARD_HEIGHT - 1 - y;
7083     }
7084     if (flipView && x >= 0) {
7085         x = BOARD_WIDTH - 1 - x;
7086     }
7087
7088     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7089         defaultPromoChoice = promoSweep;
7090         promoSweep = EmptySquare;   // terminate sweep
7091         promoDefaultAltered = TRUE;
7092         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7093     }
7094
7095     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7096         if(clickType == Release) return; // ignore upclick of click-click destination
7097         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7098         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7099         if(gameInfo.holdingsWidth &&
7100                 (WhiteOnMove(currentMove)
7101                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7102                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7103             // click in right holdings, for determining promotion piece
7104             ChessSquare p = boards[currentMove][y][x];
7105             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7106             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7107             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7108                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7109                 fromX = fromY = -1;
7110                 return;
7111             }
7112         }
7113         DrawPosition(FALSE, boards[currentMove]);
7114         return;
7115     }
7116
7117     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7118     if(clickType == Press
7119             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7120               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7121               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7122         return;
7123
7124     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7125         // could be static click on premove from-square: abort premove
7126         gotPremove = 0;
7127         ClearPremoveHighlights();
7128     }
7129
7130     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7131         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7132
7133     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7134         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7135                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7136         defaultPromoChoice = DefaultPromoChoice(side);
7137     }
7138
7139     autoQueen = appData.alwaysPromoteToQueen;
7140
7141     if (fromX == -1) {
7142       int originalY = y;
7143       gatingPiece = EmptySquare;
7144       if (clickType != Press) {
7145         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7146             DragPieceEnd(xPix, yPix); dragging = 0;
7147             DrawPosition(FALSE, NULL);
7148         }
7149         return;
7150       }
7151       doubleClick = FALSE;
7152       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7153         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7154       }
7155       fromX = x; fromY = y; toX = toY = -1;
7156       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7157          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7158          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7159             /* First square */
7160             if (OKToStartUserMove(fromX, fromY)) {
7161                 second = 0;
7162                 MarkTargetSquares(0);
7163                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7164                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7165                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7166                     promoSweep = defaultPromoChoice;
7167                     selectFlag = 0; lastX = xPix; lastY = yPix;
7168                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7169                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7170                 }
7171                 if (appData.highlightDragging) {
7172                     SetHighlights(fromX, fromY, -1, -1);
7173                 } else {
7174                     ClearHighlights();
7175                 }
7176             } else fromX = fromY = -1;
7177             return;
7178         }
7179     }
7180
7181     /* fromX != -1 */
7182     if (clickType == Press && gameMode != EditPosition) {
7183         ChessSquare fromP;
7184         ChessSquare toP;
7185         int frc;
7186
7187         // ignore off-board to clicks
7188         if(y < 0 || x < 0) return;
7189
7190         /* Check if clicking again on the same color piece */
7191         fromP = boards[currentMove][fromY][fromX];
7192         toP = boards[currentMove][y][x];
7193         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7194         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7195              WhitePawn <= toP && toP <= WhiteKing &&
7196              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7197              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7198             (BlackPawn <= fromP && fromP <= BlackKing &&
7199              BlackPawn <= toP && toP <= BlackKing &&
7200              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7201              !(fromP == BlackKing && toP == BlackRook && frc))) {
7202             /* Clicked again on same color piece -- changed his mind */
7203             second = (x == fromX && y == fromY);
7204             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7205                 second = FALSE; // first double-click rather than scond click
7206                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7207             }
7208             promoDefaultAltered = FALSE;
7209             MarkTargetSquares(1);
7210            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7211             if (appData.highlightDragging) {
7212                 SetHighlights(x, y, -1, -1);
7213             } else {
7214                 ClearHighlights();
7215             }
7216             if (OKToStartUserMove(x, y)) {
7217                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7218                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7219                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7220                  gatingPiece = boards[currentMove][fromY][fromX];
7221                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7222                 fromX = x;
7223                 fromY = y; dragging = 1;
7224                 MarkTargetSquares(0);
7225                 DragPieceBegin(xPix, yPix, FALSE);
7226                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7227                     promoSweep = defaultPromoChoice;
7228                     selectFlag = 0; lastX = xPix; lastY = yPix;
7229                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7230                 }
7231             }
7232            }
7233            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7234            second = FALSE;
7235         }
7236         // ignore clicks on holdings
7237         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7238     }
7239
7240     if (clickType == Release && x == fromX && y == fromY) {
7241         DragPieceEnd(xPix, yPix); dragging = 0;
7242         if(clearFlag) {
7243             // a deferred attempt to click-click move an empty square on top of a piece
7244             boards[currentMove][y][x] = EmptySquare;
7245             ClearHighlights();
7246             DrawPosition(FALSE, boards[currentMove]);
7247             fromX = fromY = -1; clearFlag = 0;
7248             return;
7249         }
7250         if (appData.animateDragging) {
7251             /* Undo animation damage if any */
7252             DrawPosition(FALSE, NULL);
7253         }
7254         if (second || sweepSelecting) {
7255             /* Second up/down in same square; just abort move */
7256             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7257             second = sweepSelecting = 0;
7258             fromX = fromY = -1;
7259             gatingPiece = EmptySquare;
7260             MarkTargetSquares(1);
7261             ClearHighlights();
7262             gotPremove = 0;
7263             ClearPremoveHighlights();
7264         } else {
7265             /* First upclick in same square; start click-click mode */
7266             SetHighlights(x, y, -1, -1);
7267         }
7268         return;
7269     }
7270
7271     clearFlag = 0;
7272
7273     /* we now have a different from- and (possibly off-board) to-square */
7274     /* Completed move */
7275     if(!sweepSelecting) {
7276         toX = x;
7277         toY = y;
7278     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7279
7280     saveAnimate = appData.animate;
7281     if (clickType == Press) {
7282         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7283             // must be Edit Position mode with empty-square selected
7284             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7285             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7286             return;
7287         }
7288         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7289           if(appData.sweepSelect) {
7290             ChessSquare piece = boards[currentMove][fromY][fromX];
7291             promoSweep = defaultPromoChoice;
7292             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7293             selectFlag = 0; lastX = xPix; lastY = yPix;
7294             Sweep(0); // Pawn that is going to promote: preview promotion piece
7295             sweepSelecting = 1;
7296             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7297             MarkTargetSquares(1);
7298           }
7299           return; // promo popup appears on up-click
7300         }
7301         /* Finish clickclick move */
7302         if (appData.animate || appData.highlightLastMove) {
7303             SetHighlights(fromX, fromY, toX, toY);
7304         } else {
7305             ClearHighlights();
7306         }
7307     } else {
7308 #if 0
7309 // [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
7310         /* Finish drag move */
7311         if (appData.highlightLastMove) {
7312             SetHighlights(fromX, fromY, toX, toY);
7313         } else {
7314             ClearHighlights();
7315         }
7316 #endif
7317         DragPieceEnd(xPix, yPix); dragging = 0;
7318         /* Don't animate move and drag both */
7319         appData.animate = FALSE;
7320     }
7321
7322     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7323     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7324         ChessSquare piece = boards[currentMove][fromY][fromX];
7325         if(gameMode == EditPosition && piece != EmptySquare &&
7326            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7327             int n;
7328
7329             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7330                 n = PieceToNumber(piece - (int)BlackPawn);
7331                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7332                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7333                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7334             } else
7335             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7336                 n = PieceToNumber(piece);
7337                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7338                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7339                 boards[currentMove][n][BOARD_WIDTH-2]++;
7340             }
7341             boards[currentMove][fromY][fromX] = EmptySquare;
7342         }
7343         ClearHighlights();
7344         fromX = fromY = -1;
7345         MarkTargetSquares(1);
7346         DrawPosition(TRUE, boards[currentMove]);
7347         return;
7348     }
7349
7350     // off-board moves should not be highlighted
7351     if(x < 0 || y < 0) ClearHighlights();
7352
7353     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7354
7355     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7356         SetHighlights(fromX, fromY, toX, toY);
7357         MarkTargetSquares(1);
7358         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7359             // [HGM] super: promotion to captured piece selected from holdings
7360             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7361             promotionChoice = TRUE;
7362             // kludge follows to temporarily execute move on display, without promoting yet
7363             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7364             boards[currentMove][toY][toX] = p;
7365             DrawPosition(FALSE, boards[currentMove]);
7366             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7367             boards[currentMove][toY][toX] = q;
7368             DisplayMessage("Click in holdings to choose piece", "");
7369             return;
7370         }
7371         PromotionPopUp();
7372     } else {
7373         int oldMove = currentMove;
7374         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7375         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7376         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7377         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7378            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7379             DrawPosition(TRUE, boards[currentMove]);
7380         MarkTargetSquares(1);
7381         fromX = fromY = -1;
7382     }
7383     appData.animate = saveAnimate;
7384     if (appData.animate || appData.animateDragging) {
7385         /* Undo animation damage if needed */
7386         DrawPosition(FALSE, NULL);
7387     }
7388 }
7389
7390 int
7391 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7392 {   // front-end-free part taken out of PieceMenuPopup
7393     int whichMenu; int xSqr, ySqr;
7394
7395     if(seekGraphUp) { // [HGM] seekgraph
7396         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7397         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7398         return -2;
7399     }
7400
7401     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7402          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7403         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7404         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7405         if(action == Press)   {
7406             originalFlip = flipView;
7407             flipView = !flipView; // temporarily flip board to see game from partners perspective
7408             DrawPosition(TRUE, partnerBoard);
7409             DisplayMessage(partnerStatus, "");
7410             partnerUp = TRUE;
7411         } else if(action == Release) {
7412             flipView = originalFlip;
7413             DrawPosition(TRUE, boards[currentMove]);
7414             partnerUp = FALSE;
7415         }
7416         return -2;
7417     }
7418
7419     xSqr = EventToSquare(x, BOARD_WIDTH);
7420     ySqr = EventToSquare(y, BOARD_HEIGHT);
7421     if (action == Release) {
7422         if(pieceSweep != EmptySquare) {
7423             EditPositionMenuEvent(pieceSweep, toX, toY);
7424             pieceSweep = EmptySquare;
7425         } else UnLoadPV(); // [HGM] pv
7426     }
7427     if (action != Press) return -2; // return code to be ignored
7428     switch (gameMode) {
7429       case IcsExamining:
7430         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7431       case EditPosition:
7432         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7433         if (xSqr < 0 || ySqr < 0) return -1;
7434         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7435         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7436         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7437         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7438         NextPiece(0);
7439         return 2; // grab
7440       case IcsObserving:
7441         if(!appData.icsEngineAnalyze) return -1;
7442       case IcsPlayingWhite:
7443       case IcsPlayingBlack:
7444         if(!appData.zippyPlay) goto noZip;
7445       case AnalyzeMode:
7446       case AnalyzeFile:
7447       case MachinePlaysWhite:
7448       case MachinePlaysBlack:
7449       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7450         if (!appData.dropMenu) {
7451           LoadPV(x, y);
7452           return 2; // flag front-end to grab mouse events
7453         }
7454         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7455            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7456       case EditGame:
7457       noZip:
7458         if (xSqr < 0 || ySqr < 0) return -1;
7459         if (!appData.dropMenu || appData.testLegality &&
7460             gameInfo.variant != VariantBughouse &&
7461             gameInfo.variant != VariantCrazyhouse) return -1;
7462         whichMenu = 1; // drop menu
7463         break;
7464       default:
7465         return -1;
7466     }
7467
7468     if (((*fromX = xSqr) < 0) ||
7469         ((*fromY = ySqr) < 0)) {
7470         *fromX = *fromY = -1;
7471         return -1;
7472     }
7473     if (flipView)
7474       *fromX = BOARD_WIDTH - 1 - *fromX;
7475     else
7476       *fromY = BOARD_HEIGHT - 1 - *fromY;
7477
7478     return whichMenu;
7479 }
7480
7481 void
7482 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7483 {
7484 //    char * hint = lastHint;
7485     FrontEndProgramStats stats;
7486
7487     stats.which = cps == &first ? 0 : 1;
7488     stats.depth = cpstats->depth;
7489     stats.nodes = cpstats->nodes;
7490     stats.score = cpstats->score;
7491     stats.time = cpstats->time;
7492     stats.pv = cpstats->movelist;
7493     stats.hint = lastHint;
7494     stats.an_move_index = 0;
7495     stats.an_move_count = 0;
7496
7497     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7498         stats.hint = cpstats->move_name;
7499         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7500         stats.an_move_count = cpstats->nr_moves;
7501     }
7502
7503     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
7504
7505     SetProgramStats( &stats );
7506 }
7507
7508 void
7509 ClearEngineOutputPane (int which)
7510 {
7511     static FrontEndProgramStats dummyStats;
7512     dummyStats.which = which;
7513     dummyStats.pv = "#";
7514     SetProgramStats( &dummyStats );
7515 }
7516
7517 #define MAXPLAYERS 500
7518
7519 char *
7520 TourneyStandings (int display)
7521 {
7522     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7523     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7524     char result, *p, *names[MAXPLAYERS];
7525
7526     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7527         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7528     names[0] = p = strdup(appData.participants);
7529     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7530
7531     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7532
7533     while(result = appData.results[nr]) {
7534         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7535         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7536         wScore = bScore = 0;
7537         switch(result) {
7538           case '+': wScore = 2; break;
7539           case '-': bScore = 2; break;
7540           case '=': wScore = bScore = 1; break;
7541           case ' ':
7542           case '*': return strdup("busy"); // tourney not finished
7543         }
7544         score[w] += wScore;
7545         score[b] += bScore;
7546         games[w]++;
7547         games[b]++;
7548         nr++;
7549     }
7550     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7551     for(w=0; w<nPlayers; w++) {
7552         bScore = -1;
7553         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7554         ranking[w] = b; points[w] = bScore; score[b] = -2;
7555     }
7556     p = malloc(nPlayers*34+1);
7557     for(w=0; w<nPlayers && w<display; w++)
7558         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7559     free(names[0]);
7560     return p;
7561 }
7562
7563 void
7564 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7565 {       // count all piece types
7566         int p, f, r;
7567         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7568         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7569         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7570                 p = board[r][f];
7571                 pCnt[p]++;
7572                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7573                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7574                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7575                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7576                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7577                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7578         }
7579 }
7580
7581 int
7582 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7583 {
7584         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7585         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7586
7587         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7588         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7589         if(myPawns == 2 && nMine == 3) // KPP
7590             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7591         if(myPawns == 1 && nMine == 2) // KP
7592             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7593         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7594             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7595         if(myPawns) return FALSE;
7596         if(pCnt[WhiteRook+side])
7597             return pCnt[BlackRook-side] ||
7598                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7599                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7600                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7601         if(pCnt[WhiteCannon+side]) {
7602             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7603             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7604         }
7605         if(pCnt[WhiteKnight+side])
7606             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7607         return FALSE;
7608 }
7609
7610 int
7611 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7612 {
7613         VariantClass v = gameInfo.variant;
7614
7615         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7616         if(v == VariantShatranj) return TRUE; // always winnable through baring
7617         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7618         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7619
7620         if(v == VariantXiangqi) {
7621                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7622
7623                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7624                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7625                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7626                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7627                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7628                 if(stale) // we have at least one last-rank P plus perhaps C
7629                     return majors // KPKX
7630                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7631                 else // KCA*E*
7632                     return pCnt[WhiteFerz+side] // KCAK
7633                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7634                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7635                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7636
7637         } else if(v == VariantKnightmate) {
7638                 if(nMine == 1) return FALSE;
7639                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7640         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7641                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7642
7643                 if(nMine == 1) return FALSE; // bare King
7644                 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
7645                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7646                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7647                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7648                 if(pCnt[WhiteKnight+side])
7649                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7650                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7651                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7652                 if(nBishops)
7653                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7654                 if(pCnt[WhiteAlfil+side])
7655                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7656                 if(pCnt[WhiteWazir+side])
7657                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7658         }
7659
7660         return TRUE;
7661 }
7662
7663 int
7664 CompareWithRights (Board b1, Board b2)
7665 {
7666     int rights = 0;
7667     if(!CompareBoards(b1, b2)) return FALSE;
7668     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7669     /* compare castling rights */
7670     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7671            rights++; /* King lost rights, while rook still had them */
7672     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7673         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7674            rights++; /* but at least one rook lost them */
7675     }
7676     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7677            rights++;
7678     if( b1[CASTLING][5] != NoRights ) {
7679         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7680            rights++;
7681     }
7682     return rights == 0;
7683 }
7684
7685 int
7686 Adjudicate (ChessProgramState *cps)
7687 {       // [HGM] some adjudications useful with buggy engines
7688         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7689         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7690         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7691         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7692         int k, drop, count = 0; static int bare = 1;
7693         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7694         Boolean canAdjudicate = !appData.icsActive;
7695
7696         // most tests only when we understand the game, i.e. legality-checking on
7697             if( appData.testLegality )
7698             {   /* [HGM] Some more adjudications for obstinate engines */
7699                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7700                 static int moveCount = 6;
7701                 ChessMove result;
7702                 char *reason = NULL;
7703
7704                 /* Count what is on board. */
7705                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7706
7707                 /* Some material-based adjudications that have to be made before stalemate test */
7708                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7709                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7710                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7711                      if(canAdjudicate && appData.checkMates) {
7712                          if(engineOpponent)
7713                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7714                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7715                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7716                          return 1;
7717                      }
7718                 }
7719
7720                 /* Bare King in Shatranj (loses) or Losers (wins) */
7721                 if( nrW == 1 || nrB == 1) {
7722                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7723                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7724                      if(canAdjudicate && appData.checkMates) {
7725                          if(engineOpponent)
7726                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7727                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7728                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7729                          return 1;
7730                      }
7731                   } else
7732                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7733                   {    /* bare King */
7734                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7735                         if(canAdjudicate && appData.checkMates) {
7736                             /* but only adjudicate if adjudication enabled */
7737                             if(engineOpponent)
7738                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7739                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7740                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7741                             return 1;
7742                         }
7743                   }
7744                 } else bare = 1;
7745
7746
7747             // don't wait for engine to announce game end if we can judge ourselves
7748             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7749               case MT_CHECK:
7750                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7751                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7752                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7753                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7754                             checkCnt++;
7755                         if(checkCnt >= 2) {
7756                             reason = "Xboard adjudication: 3rd check";
7757                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7758                             break;
7759                         }
7760                     }
7761                 }
7762               case MT_NONE:
7763               default:
7764                 break;
7765               case MT_STEALMATE:
7766               case MT_STALEMATE:
7767               case MT_STAINMATE:
7768                 reason = "Xboard adjudication: Stalemate";
7769                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7770                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7771                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7772                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7773                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7774                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7775                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7776                                                                         EP_CHECKMATE : EP_WINS);
7777                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7778                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7779                 }
7780                 break;
7781               case MT_CHECKMATE:
7782                 reason = "Xboard adjudication: Checkmate";
7783                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7784                 if(gameInfo.variant == VariantShogi) {
7785                     if(forwardMostMove > backwardMostMove
7786                        && moveList[forwardMostMove-1][1] == '@'
7787                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7788                         reason = "XBoard adjudication: pawn-drop mate";
7789                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7790                     }
7791                 }
7792                 break;
7793             }
7794
7795                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7796                     case EP_STALEMATE:
7797                         result = GameIsDrawn; break;
7798                     case EP_CHECKMATE:
7799                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7800                     case EP_WINS:
7801                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7802                     default:
7803                         result = EndOfFile;
7804                 }
7805                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7806                     if(engineOpponent)
7807                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7808                     GameEnds( result, reason, GE_XBOARD );
7809                     return 1;
7810                 }
7811
7812                 /* Next absolutely insufficient mating material. */
7813                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7814                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7815                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7816
7817                      /* always flag draws, for judging claims */
7818                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7819
7820                      if(canAdjudicate && appData.materialDraws) {
7821                          /* but only adjudicate them if adjudication enabled */
7822                          if(engineOpponent) {
7823                            SendToProgram("force\n", engineOpponent); // suppress reply
7824                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7825                          }
7826                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7827                          return 1;
7828                      }
7829                 }
7830
7831                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7832                 if(gameInfo.variant == VariantXiangqi ?
7833                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7834                  : nrW + nrB == 4 &&
7835                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7836                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7837                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7838                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7839                    ) ) {
7840                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7841                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7842                           if(engineOpponent) {
7843                             SendToProgram("force\n", engineOpponent); // suppress reply
7844                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7845                           }
7846                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7847                           return 1;
7848                      }
7849                 } else moveCount = 6;
7850             }
7851
7852         // Repetition draws and 50-move rule can be applied independently of legality testing
7853
7854                 /* Check for rep-draws */
7855                 count = 0;
7856                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7857                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7858                 for(k = forwardMostMove-2;
7859                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7860                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7861                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7862                     k-=2)
7863                 {   int rights=0;
7864                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7865                         /* compare castling rights */
7866                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7867                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7868                                 rights++; /* King lost rights, while rook still had them */
7869                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7870                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7871                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7872                                    rights++; /* but at least one rook lost them */
7873                         }
7874                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7875                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7876                                 rights++;
7877                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7878                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7879                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7880                                    rights++;
7881                         }
7882                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7883                             && appData.drawRepeats > 1) {
7884                              /* adjudicate after user-specified nr of repeats */
7885                              int result = GameIsDrawn;
7886                              char *details = "XBoard adjudication: repetition draw";
7887                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7888                                 // [HGM] xiangqi: check for forbidden perpetuals
7889                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7890                                 for(m=forwardMostMove; m>k; m-=2) {
7891                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7892                                         ourPerpetual = 0; // the current mover did not always check
7893                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7894                                         hisPerpetual = 0; // the opponent did not always check
7895                                 }
7896                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7897                                                                         ourPerpetual, hisPerpetual);
7898                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7899                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7900                                     details = "Xboard adjudication: perpetual checking";
7901                                 } else
7902                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7903                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7904                                 } else
7905                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7906                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7907                                         result = BlackWins;
7908                                         details = "Xboard adjudication: repetition";
7909                                     }
7910                                 } else // it must be XQ
7911                                 // Now check for perpetual chases
7912                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7913                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7914                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7915                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7916                                         static char resdet[MSG_SIZ];
7917                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7918                                         details = resdet;
7919                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7920                                     } else
7921                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7922                                         break; // Abort repetition-checking loop.
7923                                 }
7924                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7925                              }
7926                              if(engineOpponent) {
7927                                SendToProgram("force\n", engineOpponent); // suppress reply
7928                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7929                              }
7930                              GameEnds( result, details, GE_XBOARD );
7931                              return 1;
7932                         }
7933                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7934                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7935                     }
7936                 }
7937
7938                 /* Now we test for 50-move draws. Determine ply count */
7939                 count = forwardMostMove;
7940                 /* look for last irreversble move */
7941                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7942                     count--;
7943                 /* if we hit starting position, add initial plies */
7944                 if( count == backwardMostMove )
7945                     count -= initialRulePlies;
7946                 count = forwardMostMove - count;
7947                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7948                         // adjust reversible move counter for checks in Xiangqi
7949                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7950                         if(i < backwardMostMove) i = backwardMostMove;
7951                         while(i <= forwardMostMove) {
7952                                 lastCheck = inCheck; // check evasion does not count
7953                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7954                                 if(inCheck || lastCheck) count--; // check does not count
7955                                 i++;
7956                         }
7957                 }
7958                 if( count >= 100)
7959                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7960                          /* this is used to judge if draw claims are legal */
7961                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7962                          if(engineOpponent) {
7963                            SendToProgram("force\n", engineOpponent); // suppress reply
7964                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7965                          }
7966                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7967                          return 1;
7968                 }
7969
7970                 /* if draw offer is pending, treat it as a draw claim
7971                  * when draw condition present, to allow engines a way to
7972                  * claim draws before making their move to avoid a race
7973                  * condition occurring after their move
7974                  */
7975                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7976                          char *p = NULL;
7977                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7978                              p = "Draw claim: 50-move rule";
7979                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7980                              p = "Draw claim: 3-fold repetition";
7981                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7982                              p = "Draw claim: insufficient mating material";
7983                          if( p != NULL && canAdjudicate) {
7984                              if(engineOpponent) {
7985                                SendToProgram("force\n", engineOpponent); // suppress reply
7986                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7987                              }
7988                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7989                              return 1;
7990                          }
7991                 }
7992
7993                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7994                     if(engineOpponent) {
7995                       SendToProgram("force\n", engineOpponent); // suppress reply
7996                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7997                     }
7998                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7999                     return 1;
8000                 }
8001         return 0;
8002 }
8003
8004 char *
8005 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8006 {   // [HGM] book: this routine intercepts moves to simulate book replies
8007     char *bookHit = NULL;
8008
8009     //first determine if the incoming move brings opponent into his book
8010     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8011         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8012     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8013     if(bookHit != NULL && !cps->bookSuspend) {
8014         // make sure opponent is not going to reply after receiving move to book position
8015         SendToProgram("force\n", cps);
8016         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8017     }
8018     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8019     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8020     // now arrange restart after book miss
8021     if(bookHit) {
8022         // after a book hit we never send 'go', and the code after the call to this routine
8023         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8024         char buf[MSG_SIZ], *move = bookHit;
8025         if(cps->useSAN) {
8026             int fromX, fromY, toX, toY;
8027             char promoChar;
8028             ChessMove moveType;
8029             move = buf + 30;
8030             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8031                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8032                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8033                                     PosFlags(forwardMostMove),
8034                                     fromY, fromX, toY, toX, promoChar, move);
8035             } else {
8036                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8037                 bookHit = NULL;
8038             }
8039         }
8040         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8041         SendToProgram(buf, cps);
8042         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8043     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8044         SendToProgram("go\n", cps);
8045         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8046     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8047         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8048             SendToProgram("go\n", cps);
8049         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8050     }
8051     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8052 }
8053
8054 int
8055 LoadError (char *errmess, ChessProgramState *cps)
8056 {   // unloads engine and switches back to -ncp mode if it was first
8057     if(cps->initDone) return FALSE;
8058     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8059     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8060     cps->pr = NoProc;
8061     if(cps == &first) {
8062         appData.noChessProgram = TRUE;
8063         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8064         gameMode = BeginningOfGame; ModeHighlight();
8065         SetNCPMode();
8066     }
8067     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8068     DisplayMessage("", ""); // erase waiting message
8069     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8070     return TRUE;
8071 }
8072
8073 char *savedMessage;
8074 ChessProgramState *savedState;
8075 void
8076 DeferredBookMove (void)
8077 {
8078         if(savedState->lastPing != savedState->lastPong)
8079                     ScheduleDelayedEvent(DeferredBookMove, 10);
8080         else
8081         HandleMachineMove(savedMessage, savedState);
8082 }
8083
8084 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8085 static ChessProgramState *stalledEngine;
8086 static char stashedInputMove[MSG_SIZ];
8087
8088 void
8089 HandleMachineMove (char *message, ChessProgramState *cps)
8090 {
8091     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8092     char realname[MSG_SIZ];
8093     int fromX, fromY, toX, toY;
8094     ChessMove moveType;
8095     char promoChar;
8096     char *p, *pv=buf1;
8097     int machineWhite, oldError;
8098     char *bookHit;
8099
8100     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8101         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8102         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8103             DisplayError(_("Invalid pairing from pairing engine"), 0);
8104             return;
8105         }
8106         pairingReceived = 1;
8107         NextMatchGame();
8108         return; // Skim the pairing messages here.
8109     }
8110
8111     oldError = cps->userError; cps->userError = 0;
8112
8113 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8114     /*
8115      * Kludge to ignore BEL characters
8116      */
8117     while (*message == '\007') message++;
8118
8119     /*
8120      * [HGM] engine debug message: ignore lines starting with '#' character
8121      */
8122     if(cps->debug && *message == '#') return;
8123
8124     /*
8125      * Look for book output
8126      */
8127     if (cps == &first && bookRequested) {
8128         if (message[0] == '\t' || message[0] == ' ') {
8129             /* Part of the book output is here; append it */
8130             strcat(bookOutput, message);
8131             strcat(bookOutput, "  \n");
8132             return;
8133         } else if (bookOutput[0] != NULLCHAR) {
8134             /* All of book output has arrived; display it */
8135             char *p = bookOutput;
8136             while (*p != NULLCHAR) {
8137                 if (*p == '\t') *p = ' ';
8138                 p++;
8139             }
8140             DisplayInformation(bookOutput);
8141             bookRequested = FALSE;
8142             /* Fall through to parse the current output */
8143         }
8144     }
8145
8146     /*
8147      * Look for machine move.
8148      */
8149     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8150         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8151     {
8152         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8153             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8154             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8155             stalledEngine = cps;
8156             if(appData.ponderNextMove) { // bring opponent out of ponder
8157                 if(gameMode == TwoMachinesPlay) {
8158                     if(cps->other->pause)
8159                         PauseEngine(cps->other);
8160                     else
8161                         SendToProgram("easy\n", cps->other);
8162                 }
8163             }
8164             StopClocks();
8165             return;
8166         }
8167
8168         /* This method is only useful on engines that support ping */
8169         if (cps->lastPing != cps->lastPong) {
8170           if (gameMode == BeginningOfGame) {
8171             /* Extra move from before last new; ignore */
8172             if (appData.debugMode) {
8173                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8174             }
8175           } else {
8176             if (appData.debugMode) {
8177                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8178                         cps->which, gameMode);
8179             }
8180
8181             SendToProgram("undo\n", cps);
8182           }
8183           return;
8184         }
8185
8186         switch (gameMode) {
8187           case BeginningOfGame:
8188             /* Extra move from before last reset; ignore */
8189             if (appData.debugMode) {
8190                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8191             }
8192             return;
8193
8194           case EndOfGame:
8195           case IcsIdle:
8196           default:
8197             /* Extra move after we tried to stop.  The mode test is
8198                not a reliable way of detecting this problem, but it's
8199                the best we can do on engines that don't support ping.
8200             */
8201             if (appData.debugMode) {
8202                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8203                         cps->which, gameMode);
8204             }
8205             SendToProgram("undo\n", cps);
8206             return;
8207
8208           case MachinePlaysWhite:
8209           case IcsPlayingWhite:
8210             machineWhite = TRUE;
8211             break;
8212
8213           case MachinePlaysBlack:
8214           case IcsPlayingBlack:
8215             machineWhite = FALSE;
8216             break;
8217
8218           case TwoMachinesPlay:
8219             machineWhite = (cps->twoMachinesColor[0] == 'w');
8220             break;
8221         }
8222         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8223             if (appData.debugMode) {
8224                 fprintf(debugFP,
8225                         "Ignoring move out of turn by %s, gameMode %d"
8226                         ", forwardMost %d\n",
8227                         cps->which, gameMode, forwardMostMove);
8228             }
8229             return;
8230         }
8231
8232         if(cps->alphaRank) AlphaRank(machineMove, 4);
8233         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8234                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8235             /* Machine move could not be parsed; ignore it. */
8236           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8237                     machineMove, _(cps->which));
8238             DisplayMoveError(buf1);
8239             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8240                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8241             if (gameMode == TwoMachinesPlay) {
8242               GameEnds(machineWhite ? BlackWins : WhiteWins,
8243                        buf1, GE_XBOARD);
8244             }
8245             return;
8246         }
8247
8248         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8249         /* So we have to redo legality test with true e.p. status here,  */
8250         /* to make sure an illegal e.p. capture does not slip through,   */
8251         /* to cause a forfeit on a justified illegal-move complaint      */
8252         /* of the opponent.                                              */
8253         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8254            ChessMove moveType;
8255            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8256                              fromY, fromX, toY, toX, promoChar);
8257             if(moveType == IllegalMove) {
8258               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8259                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8260                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8261                            buf1, GE_XBOARD);
8262                 return;
8263            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8264            /* [HGM] Kludge to handle engines that send FRC-style castling
8265               when they shouldn't (like TSCP-Gothic) */
8266            switch(moveType) {
8267              case WhiteASideCastleFR:
8268              case BlackASideCastleFR:
8269                toX+=2;
8270                currentMoveString[2]++;
8271                break;
8272              case WhiteHSideCastleFR:
8273              case BlackHSideCastleFR:
8274                toX--;
8275                currentMoveString[2]--;
8276                break;
8277              default: ; // nothing to do, but suppresses warning of pedantic compilers
8278            }
8279         }
8280         hintRequested = FALSE;
8281         lastHint[0] = NULLCHAR;
8282         bookRequested = FALSE;
8283         /* Program may be pondering now */
8284         cps->maybeThinking = TRUE;
8285         if (cps->sendTime == 2) cps->sendTime = 1;
8286         if (cps->offeredDraw) cps->offeredDraw--;
8287
8288         /* [AS] Save move info*/
8289         pvInfoList[ forwardMostMove ].score = programStats.score;
8290         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8291         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8292
8293         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8294
8295         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8296         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8297             int count = 0;
8298
8299             while( count < adjudicateLossPlies ) {
8300                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8301
8302                 if( count & 1 ) {
8303                     score = -score; /* Flip score for winning side */
8304                 }
8305
8306                 if( score > adjudicateLossThreshold ) {
8307                     break;
8308                 }
8309
8310                 count++;
8311             }
8312
8313             if( count >= adjudicateLossPlies ) {
8314                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8315
8316                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8317                     "Xboard adjudication",
8318                     GE_XBOARD );
8319
8320                 return;
8321             }
8322         }
8323
8324         if(Adjudicate(cps)) {
8325             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8326             return; // [HGM] adjudicate: for all automatic game ends
8327         }
8328
8329 #if ZIPPY
8330         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8331             first.initDone) {
8332           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8333                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8334                 SendToICS("draw ");
8335                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8336           }
8337           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8338           ics_user_moved = 1;
8339           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8340                 char buf[3*MSG_SIZ];
8341
8342                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8343                         programStats.score / 100.,
8344                         programStats.depth,
8345                         programStats.time / 100.,
8346                         (unsigned int)programStats.nodes,
8347                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8348                         programStats.movelist);
8349                 SendToICS(buf);
8350 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8351           }
8352         }
8353 #endif
8354
8355         /* [AS] Clear stats for next move */
8356         ClearProgramStats();
8357         thinkOutput[0] = NULLCHAR;
8358         hiddenThinkOutputState = 0;
8359
8360         bookHit = NULL;
8361         if (gameMode == TwoMachinesPlay) {
8362             /* [HGM] relaying draw offers moved to after reception of move */
8363             /* and interpreting offer as claim if it brings draw condition */
8364             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8365                 SendToProgram("draw\n", cps->other);
8366             }
8367             if (cps->other->sendTime) {
8368                 SendTimeRemaining(cps->other,
8369                                   cps->other->twoMachinesColor[0] == 'w');
8370             }
8371             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8372             if (firstMove && !bookHit) {
8373                 firstMove = FALSE;
8374                 if (cps->other->useColors) {
8375                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8376                 }
8377                 SendToProgram("go\n", cps->other);
8378             }
8379             cps->other->maybeThinking = TRUE;
8380         }
8381
8382         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8383
8384         if (!pausing && appData.ringBellAfterMoves) {
8385             RingBell();
8386         }
8387
8388         /*
8389          * Reenable menu items that were disabled while
8390          * machine was thinking
8391          */
8392         if (gameMode != TwoMachinesPlay)
8393             SetUserThinkingEnables();
8394
8395         // [HGM] book: after book hit opponent has received move and is now in force mode
8396         // force the book reply into it, and then fake that it outputted this move by jumping
8397         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8398         if(bookHit) {
8399                 static char bookMove[MSG_SIZ]; // a bit generous?
8400
8401                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8402                 strcat(bookMove, bookHit);
8403                 message = bookMove;
8404                 cps = cps->other;
8405                 programStats.nodes = programStats.depth = programStats.time =
8406                 programStats.score = programStats.got_only_move = 0;
8407                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8408
8409                 if(cps->lastPing != cps->lastPong) {
8410                     savedMessage = message; // args for deferred call
8411                     savedState = cps;
8412                     ScheduleDelayedEvent(DeferredBookMove, 10);
8413                     return;
8414                 }
8415                 goto FakeBookMove;
8416         }
8417
8418         return;
8419     }
8420
8421     /* Set special modes for chess engines.  Later something general
8422      *  could be added here; for now there is just one kludge feature,
8423      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8424      *  when "xboard" is given as an interactive command.
8425      */
8426     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8427         cps->useSigint = FALSE;
8428         cps->useSigterm = FALSE;
8429     }
8430     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8431       ParseFeatures(message+8, cps);
8432       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8433     }
8434
8435     if (!strncmp(message, "setup ", 6) && 
8436         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8437                                         ) { // [HGM] allow first engine to define opening position
8438       int dummy, s=6; char buf[MSG_SIZ];
8439       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8440       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8441       if(startedFromSetupPosition) return;
8442       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8443       ParseFEN(boards[0], &dummy, message+s);
8444       DrawPosition(TRUE, boards[0]);
8445       startedFromSetupPosition = TRUE;
8446       return;
8447     }
8448     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8449      * want this, I was asked to put it in, and obliged.
8450      */
8451     if (!strncmp(message, "setboard ", 9)) {
8452         Board initial_position;
8453
8454         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8455
8456         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8457             DisplayError(_("Bad FEN received from engine"), 0);
8458             return ;
8459         } else {
8460            Reset(TRUE, FALSE);
8461            CopyBoard(boards[0], initial_position);
8462            initialRulePlies = FENrulePlies;
8463            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8464            else gameMode = MachinePlaysBlack;
8465            DrawPosition(FALSE, boards[currentMove]);
8466         }
8467         return;
8468     }
8469
8470     /*
8471      * Look for communication commands
8472      */
8473     if (!strncmp(message, "telluser ", 9)) {
8474         if(message[9] == '\\' && message[10] == '\\')
8475             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8476         PlayTellSound();
8477         DisplayNote(message + 9);
8478         return;
8479     }
8480     if (!strncmp(message, "tellusererror ", 14)) {
8481         cps->userError = 1;
8482         if(message[14] == '\\' && message[15] == '\\')
8483             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8484         PlayTellSound();
8485         DisplayError(message + 14, 0);
8486         return;
8487     }
8488     if (!strncmp(message, "tellopponent ", 13)) {
8489       if (appData.icsActive) {
8490         if (loggedOn) {
8491           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8492           SendToICS(buf1);
8493         }
8494       } else {
8495         DisplayNote(message + 13);
8496       }
8497       return;
8498     }
8499     if (!strncmp(message, "tellothers ", 11)) {
8500       if (appData.icsActive) {
8501         if (loggedOn) {
8502           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8503           SendToICS(buf1);
8504         }
8505       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8506       return;
8507     }
8508     if (!strncmp(message, "tellall ", 8)) {
8509       if (appData.icsActive) {
8510         if (loggedOn) {
8511           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8512           SendToICS(buf1);
8513         }
8514       } else {
8515         DisplayNote(message + 8);
8516       }
8517       return;
8518     }
8519     if (strncmp(message, "warning", 7) == 0) {
8520         /* Undocumented feature, use tellusererror in new code */
8521         DisplayError(message, 0);
8522         return;
8523     }
8524     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8525         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8526         strcat(realname, " query");
8527         AskQuestion(realname, buf2, buf1, cps->pr);
8528         return;
8529     }
8530     /* Commands from the engine directly to ICS.  We don't allow these to be
8531      *  sent until we are logged on. Crafty kibitzes have been known to
8532      *  interfere with the login process.
8533      */
8534     if (loggedOn) {
8535         if (!strncmp(message, "tellics ", 8)) {
8536             SendToICS(message + 8);
8537             SendToICS("\n");
8538             return;
8539         }
8540         if (!strncmp(message, "tellicsnoalias ", 15)) {
8541             SendToICS(ics_prefix);
8542             SendToICS(message + 15);
8543             SendToICS("\n");
8544             return;
8545         }
8546         /* The following are for backward compatibility only */
8547         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8548             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8549             SendToICS(ics_prefix);
8550             SendToICS(message);
8551             SendToICS("\n");
8552             return;
8553         }
8554     }
8555     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8556         return;
8557     }
8558     /*
8559      * If the move is illegal, cancel it and redraw the board.
8560      * Also deal with other error cases.  Matching is rather loose
8561      * here to accommodate engines written before the spec.
8562      */
8563     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8564         strncmp(message, "Error", 5) == 0) {
8565         if (StrStr(message, "name") ||
8566             StrStr(message, "rating") || StrStr(message, "?") ||
8567             StrStr(message, "result") || StrStr(message, "board") ||
8568             StrStr(message, "bk") || StrStr(message, "computer") ||
8569             StrStr(message, "variant") || StrStr(message, "hint") ||
8570             StrStr(message, "random") || StrStr(message, "depth") ||
8571             StrStr(message, "accepted")) {
8572             return;
8573         }
8574         if (StrStr(message, "protover")) {
8575           /* Program is responding to input, so it's apparently done
8576              initializing, and this error message indicates it is
8577              protocol version 1.  So we don't need to wait any longer
8578              for it to initialize and send feature commands. */
8579           FeatureDone(cps, 1);
8580           cps->protocolVersion = 1;
8581           return;
8582         }
8583         cps->maybeThinking = FALSE;
8584
8585         if (StrStr(message, "draw")) {
8586             /* Program doesn't have "draw" command */
8587             cps->sendDrawOffers = 0;
8588             return;
8589         }
8590         if (cps->sendTime != 1 &&
8591             (StrStr(message, "time") || StrStr(message, "otim"))) {
8592           /* Program apparently doesn't have "time" or "otim" command */
8593           cps->sendTime = 0;
8594           return;
8595         }
8596         if (StrStr(message, "analyze")) {
8597             cps->analysisSupport = FALSE;
8598             cps->analyzing = FALSE;
8599 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8600             EditGameEvent(); // [HGM] try to preserve loaded game
8601             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8602             DisplayError(buf2, 0);
8603             return;
8604         }
8605         if (StrStr(message, "(no matching move)st")) {
8606           /* Special kludge for GNU Chess 4 only */
8607           cps->stKludge = TRUE;
8608           SendTimeControl(cps, movesPerSession, timeControl,
8609                           timeIncrement, appData.searchDepth,
8610                           searchTime);
8611           return;
8612         }
8613         if (StrStr(message, "(no matching move)sd")) {
8614           /* Special kludge for GNU Chess 4 only */
8615           cps->sdKludge = TRUE;
8616           SendTimeControl(cps, movesPerSession, timeControl,
8617                           timeIncrement, appData.searchDepth,
8618                           searchTime);
8619           return;
8620         }
8621         if (!StrStr(message, "llegal")) {
8622             return;
8623         }
8624         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8625             gameMode == IcsIdle) return;
8626         if (forwardMostMove <= backwardMostMove) return;
8627         if (pausing) PauseEvent();
8628       if(appData.forceIllegal) {
8629             // [HGM] illegal: machine refused move; force position after move into it
8630           SendToProgram("force\n", cps);
8631           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8632                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8633                 // when black is to move, while there might be nothing on a2 or black
8634                 // might already have the move. So send the board as if white has the move.
8635                 // But first we must change the stm of the engine, as it refused the last move
8636                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8637                 if(WhiteOnMove(forwardMostMove)) {
8638                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8639                     SendBoard(cps, forwardMostMove); // kludgeless board
8640                 } else {
8641                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8642                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8643                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8644                 }
8645           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8646             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8647                  gameMode == TwoMachinesPlay)
8648               SendToProgram("go\n", cps);
8649             return;
8650       } else
8651         if (gameMode == PlayFromGameFile) {
8652             /* Stop reading this game file */
8653             gameMode = EditGame;
8654             ModeHighlight();
8655         }
8656         /* [HGM] illegal-move claim should forfeit game when Xboard */
8657         /* only passes fully legal moves                            */
8658         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8659             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8660                                 "False illegal-move claim", GE_XBOARD );
8661             return; // do not take back move we tested as valid
8662         }
8663         currentMove = forwardMostMove-1;
8664         DisplayMove(currentMove-1); /* before DisplayMoveError */
8665         SwitchClocks(forwardMostMove-1); // [HGM] race
8666         DisplayBothClocks();
8667         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8668                 parseList[currentMove], _(cps->which));
8669         DisplayMoveError(buf1);
8670         DrawPosition(FALSE, boards[currentMove]);
8671
8672         SetUserThinkingEnables();
8673         return;
8674     }
8675     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8676         /* Program has a broken "time" command that
8677            outputs a string not ending in newline.
8678            Don't use it. */
8679         cps->sendTime = 0;
8680     }
8681
8682     /*
8683      * If chess program startup fails, exit with an error message.
8684      * Attempts to recover here are futile. [HGM] Well, we try anyway
8685      */
8686     if ((StrStr(message, "unknown host") != NULL)
8687         || (StrStr(message, "No remote directory") != NULL)
8688         || (StrStr(message, "not found") != NULL)
8689         || (StrStr(message, "No such file") != NULL)
8690         || (StrStr(message, "can't alloc") != NULL)
8691         || (StrStr(message, "Permission denied") != NULL)) {
8692
8693         cps->maybeThinking = FALSE;
8694         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8695                 _(cps->which), cps->program, cps->host, message);
8696         RemoveInputSource(cps->isr);
8697         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8698             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8699             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8700         }
8701         return;
8702     }
8703
8704     /*
8705      * Look for hint output
8706      */
8707     if (sscanf(message, "Hint: %s", buf1) == 1) {
8708         if (cps == &first && hintRequested) {
8709             hintRequested = FALSE;
8710             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8711                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8712                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8713                                     PosFlags(forwardMostMove),
8714                                     fromY, fromX, toY, toX, promoChar, buf1);
8715                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8716                 DisplayInformation(buf2);
8717             } else {
8718                 /* Hint move could not be parsed!? */
8719               snprintf(buf2, sizeof(buf2),
8720                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8721                         buf1, _(cps->which));
8722                 DisplayError(buf2, 0);
8723             }
8724         } else {
8725           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8726         }
8727         return;
8728     }
8729
8730     /*
8731      * Ignore other messages if game is not in progress
8732      */
8733     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8734         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8735
8736     /*
8737      * look for win, lose, draw, or draw offer
8738      */
8739     if (strncmp(message, "1-0", 3) == 0) {
8740         char *p, *q, *r = "";
8741         p = strchr(message, '{');
8742         if (p) {
8743             q = strchr(p, '}');
8744             if (q) {
8745                 *q = NULLCHAR;
8746                 r = p + 1;
8747             }
8748         }
8749         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8750         return;
8751     } else if (strncmp(message, "0-1", 3) == 0) {
8752         char *p, *q, *r = "";
8753         p = strchr(message, '{');
8754         if (p) {
8755             q = strchr(p, '}');
8756             if (q) {
8757                 *q = NULLCHAR;
8758                 r = p + 1;
8759             }
8760         }
8761         /* Kludge for Arasan 4.1 bug */
8762         if (strcmp(r, "Black resigns") == 0) {
8763             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8764             return;
8765         }
8766         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8767         return;
8768     } else if (strncmp(message, "1/2", 3) == 0) {
8769         char *p, *q, *r = "";
8770         p = strchr(message, '{');
8771         if (p) {
8772             q = strchr(p, '}');
8773             if (q) {
8774                 *q = NULLCHAR;
8775                 r = p + 1;
8776             }
8777         }
8778
8779         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8780         return;
8781
8782     } else if (strncmp(message, "White resign", 12) == 0) {
8783         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8784         return;
8785     } else if (strncmp(message, "Black resign", 12) == 0) {
8786         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8787         return;
8788     } else if (strncmp(message, "White matches", 13) == 0 ||
8789                strncmp(message, "Black matches", 13) == 0   ) {
8790         /* [HGM] ignore GNUShogi noises */
8791         return;
8792     } else if (strncmp(message, "White", 5) == 0 &&
8793                message[5] != '(' &&
8794                StrStr(message, "Black") == NULL) {
8795         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8796         return;
8797     } else if (strncmp(message, "Black", 5) == 0 &&
8798                message[5] != '(') {
8799         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8800         return;
8801     } else if (strcmp(message, "resign") == 0 ||
8802                strcmp(message, "computer resigns") == 0) {
8803         switch (gameMode) {
8804           case MachinePlaysBlack:
8805           case IcsPlayingBlack:
8806             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8807             break;
8808           case MachinePlaysWhite:
8809           case IcsPlayingWhite:
8810             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8811             break;
8812           case TwoMachinesPlay:
8813             if (cps->twoMachinesColor[0] == 'w')
8814               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8815             else
8816               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8817             break;
8818           default:
8819             /* can't happen */
8820             break;
8821         }
8822         return;
8823     } else if (strncmp(message, "opponent mates", 14) == 0) {
8824         switch (gameMode) {
8825           case MachinePlaysBlack:
8826           case IcsPlayingBlack:
8827             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8828             break;
8829           case MachinePlaysWhite:
8830           case IcsPlayingWhite:
8831             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8832             break;
8833           case TwoMachinesPlay:
8834             if (cps->twoMachinesColor[0] == 'w')
8835               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8836             else
8837               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8838             break;
8839           default:
8840             /* can't happen */
8841             break;
8842         }
8843         return;
8844     } else if (strncmp(message, "computer mates", 14) == 0) {
8845         switch (gameMode) {
8846           case MachinePlaysBlack:
8847           case IcsPlayingBlack:
8848             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8849             break;
8850           case MachinePlaysWhite:
8851           case IcsPlayingWhite:
8852             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8853             break;
8854           case TwoMachinesPlay:
8855             if (cps->twoMachinesColor[0] == 'w')
8856               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8857             else
8858               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8859             break;
8860           default:
8861             /* can't happen */
8862             break;
8863         }
8864         return;
8865     } else if (strncmp(message, "checkmate", 9) == 0) {
8866         if (WhiteOnMove(forwardMostMove)) {
8867             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8868         } else {
8869             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8870         }
8871         return;
8872     } else if (strstr(message, "Draw") != NULL ||
8873                strstr(message, "game is a draw") != NULL) {
8874         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8875         return;
8876     } else if (strstr(message, "offer") != NULL &&
8877                strstr(message, "draw") != NULL) {
8878 #if ZIPPY
8879         if (appData.zippyPlay && first.initDone) {
8880             /* Relay offer to ICS */
8881             SendToICS(ics_prefix);
8882             SendToICS("draw\n");
8883         }
8884 #endif
8885         cps->offeredDraw = 2; /* valid until this engine moves twice */
8886         if (gameMode == TwoMachinesPlay) {
8887             if (cps->other->offeredDraw) {
8888                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8889             /* [HGM] in two-machine mode we delay relaying draw offer      */
8890             /* until after we also have move, to see if it is really claim */
8891             }
8892         } else if (gameMode == MachinePlaysWhite ||
8893                    gameMode == MachinePlaysBlack) {
8894           if (userOfferedDraw) {
8895             DisplayInformation(_("Machine accepts your draw offer"));
8896             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8897           } else {
8898             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8899           }
8900         }
8901     }
8902
8903
8904     /*
8905      * Look for thinking output
8906      */
8907     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8908           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8909                                 ) {
8910         int plylev, mvleft, mvtot, curscore, time;
8911         char mvname[MOVE_LEN];
8912         u64 nodes; // [DM]
8913         char plyext;
8914         int ignore = FALSE;
8915         int prefixHint = FALSE;
8916         mvname[0] = NULLCHAR;
8917
8918         switch (gameMode) {
8919           case MachinePlaysBlack:
8920           case IcsPlayingBlack:
8921             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8922             break;
8923           case MachinePlaysWhite:
8924           case IcsPlayingWhite:
8925             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8926             break;
8927           case AnalyzeMode:
8928           case AnalyzeFile:
8929             break;
8930           case IcsObserving: /* [DM] icsEngineAnalyze */
8931             if (!appData.icsEngineAnalyze) ignore = TRUE;
8932             break;
8933           case TwoMachinesPlay:
8934             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8935                 ignore = TRUE;
8936             }
8937             break;
8938           default:
8939             ignore = TRUE;
8940             break;
8941         }
8942
8943         if (!ignore) {
8944             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8945             buf1[0] = NULLCHAR;
8946             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8947                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8948
8949                 if (plyext != ' ' && plyext != '\t') {
8950                     time *= 100;
8951                 }
8952
8953                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8954                 if( cps->scoreIsAbsolute &&
8955                     ( gameMode == MachinePlaysBlack ||
8956                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8957                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8958                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8959                      !WhiteOnMove(currentMove)
8960                     ) )
8961                 {
8962                     curscore = -curscore;
8963                 }
8964
8965                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8966
8967                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8968                         char buf[MSG_SIZ];
8969                         FILE *f;
8970                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8971                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8972                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8973                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8974                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8975                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8976                                 fclose(f);
8977                         } else DisplayError(_("failed writing PV"), 0);
8978                 }
8979
8980                 tempStats.depth = plylev;
8981                 tempStats.nodes = nodes;
8982                 tempStats.time = time;
8983                 tempStats.score = curscore;
8984                 tempStats.got_only_move = 0;
8985
8986                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8987                         int ticklen;
8988
8989                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8990                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8991                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8992                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8993                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8994                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8995                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8996                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8997                 }
8998
8999                 /* Buffer overflow protection */
9000                 if (pv[0] != NULLCHAR) {
9001                     if (strlen(pv) >= sizeof(tempStats.movelist)
9002                         && appData.debugMode) {
9003                         fprintf(debugFP,
9004                                 "PV is too long; using the first %u bytes.\n",
9005                                 (unsigned) sizeof(tempStats.movelist) - 1);
9006                     }
9007
9008                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9009                 } else {
9010                     sprintf(tempStats.movelist, " no PV\n");
9011                 }
9012
9013                 if (tempStats.seen_stat) {
9014                     tempStats.ok_to_send = 1;
9015                 }
9016
9017                 if (strchr(tempStats.movelist, '(') != NULL) {
9018                     tempStats.line_is_book = 1;
9019                     tempStats.nr_moves = 0;
9020                     tempStats.moves_left = 0;
9021                 } else {
9022                     tempStats.line_is_book = 0;
9023                 }
9024
9025                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9026                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9027
9028                 SendProgramStatsToFrontend( cps, &tempStats );
9029
9030                 /*
9031                     [AS] Protect the thinkOutput buffer from overflow... this
9032                     is only useful if buf1 hasn't overflowed first!
9033                 */
9034                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9035                          plylev,
9036                          (gameMode == TwoMachinesPlay ?
9037                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9038                          ((double) curscore) / 100.0,
9039                          prefixHint ? lastHint : "",
9040                          prefixHint ? " " : "" );
9041
9042                 if( buf1[0] != NULLCHAR ) {
9043                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9044
9045                     if( strlen(pv) > max_len ) {
9046                         if( appData.debugMode) {
9047                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9048                         }
9049                         pv[max_len+1] = '\0';
9050                     }
9051
9052                     strcat( thinkOutput, pv);
9053                 }
9054
9055                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9056                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9057                     DisplayMove(currentMove - 1);
9058                 }
9059                 return;
9060
9061             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9062                 /* crafty (9.25+) says "(only move) <move>"
9063                  * if there is only 1 legal move
9064                  */
9065                 sscanf(p, "(only move) %s", buf1);
9066                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9067                 sprintf(programStats.movelist, "%s (only move)", buf1);
9068                 programStats.depth = 1;
9069                 programStats.nr_moves = 1;
9070                 programStats.moves_left = 1;
9071                 programStats.nodes = 1;
9072                 programStats.time = 1;
9073                 programStats.got_only_move = 1;
9074
9075                 /* Not really, but we also use this member to
9076                    mean "line isn't going to change" (Crafty
9077                    isn't searching, so stats won't change) */
9078                 programStats.line_is_book = 1;
9079
9080                 SendProgramStatsToFrontend( cps, &programStats );
9081
9082                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9083                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9084                     DisplayMove(currentMove - 1);
9085                 }
9086                 return;
9087             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9088                               &time, &nodes, &plylev, &mvleft,
9089                               &mvtot, mvname) >= 5) {
9090                 /* The stat01: line is from Crafty (9.29+) in response
9091                    to the "." command */
9092                 programStats.seen_stat = 1;
9093                 cps->maybeThinking = TRUE;
9094
9095                 if (programStats.got_only_move || !appData.periodicUpdates)
9096                   return;
9097
9098                 programStats.depth = plylev;
9099                 programStats.time = time;
9100                 programStats.nodes = nodes;
9101                 programStats.moves_left = mvleft;
9102                 programStats.nr_moves = mvtot;
9103                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9104                 programStats.ok_to_send = 1;
9105                 programStats.movelist[0] = '\0';
9106
9107                 SendProgramStatsToFrontend( cps, &programStats );
9108
9109                 return;
9110
9111             } else if (strncmp(message,"++",2) == 0) {
9112                 /* Crafty 9.29+ outputs this */
9113                 programStats.got_fail = 2;
9114                 return;
9115
9116             } else if (strncmp(message,"--",2) == 0) {
9117                 /* Crafty 9.29+ outputs this */
9118                 programStats.got_fail = 1;
9119                 return;
9120
9121             } else if (thinkOutput[0] != NULLCHAR &&
9122                        strncmp(message, "    ", 4) == 0) {
9123                 unsigned message_len;
9124
9125                 p = message;
9126                 while (*p && *p == ' ') p++;
9127
9128                 message_len = strlen( p );
9129
9130                 /* [AS] Avoid buffer overflow */
9131                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9132                     strcat(thinkOutput, " ");
9133                     strcat(thinkOutput, p);
9134                 }
9135
9136                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9137                     strcat(programStats.movelist, " ");
9138                     strcat(programStats.movelist, p);
9139                 }
9140
9141                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9142                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9143                     DisplayMove(currentMove - 1);
9144                 }
9145                 return;
9146             }
9147         }
9148         else {
9149             buf1[0] = NULLCHAR;
9150
9151             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9152                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9153             {
9154                 ChessProgramStats cpstats;
9155
9156                 if (plyext != ' ' && plyext != '\t') {
9157                     time *= 100;
9158                 }
9159
9160                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9161                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9162                     curscore = -curscore;
9163                 }
9164
9165                 cpstats.depth = plylev;
9166                 cpstats.nodes = nodes;
9167                 cpstats.time = time;
9168                 cpstats.score = curscore;
9169                 cpstats.got_only_move = 0;
9170                 cpstats.movelist[0] = '\0';
9171
9172                 if (buf1[0] != NULLCHAR) {
9173                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9174                 }
9175
9176                 cpstats.ok_to_send = 0;
9177                 cpstats.line_is_book = 0;
9178                 cpstats.nr_moves = 0;
9179                 cpstats.moves_left = 0;
9180
9181                 SendProgramStatsToFrontend( cps, &cpstats );
9182             }
9183         }
9184     }
9185 }
9186
9187
9188 /* Parse a game score from the character string "game", and
9189    record it as the history of the current game.  The game
9190    score is NOT assumed to start from the standard position.
9191    The display is not updated in any way.
9192    */
9193 void
9194 ParseGameHistory (char *game)
9195 {
9196     ChessMove moveType;
9197     int fromX, fromY, toX, toY, boardIndex;
9198     char promoChar;
9199     char *p, *q;
9200     char buf[MSG_SIZ];
9201
9202     if (appData.debugMode)
9203       fprintf(debugFP, "Parsing game history: %s\n", game);
9204
9205     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9206     gameInfo.site = StrSave(appData.icsHost);
9207     gameInfo.date = PGNDate();
9208     gameInfo.round = StrSave("-");
9209
9210     /* Parse out names of players */
9211     while (*game == ' ') game++;
9212     p = buf;
9213     while (*game != ' ') *p++ = *game++;
9214     *p = NULLCHAR;
9215     gameInfo.white = StrSave(buf);
9216     while (*game == ' ') game++;
9217     p = buf;
9218     while (*game != ' ' && *game != '\n') *p++ = *game++;
9219     *p = NULLCHAR;
9220     gameInfo.black = StrSave(buf);
9221
9222     /* Parse moves */
9223     boardIndex = blackPlaysFirst ? 1 : 0;
9224     yynewstr(game);
9225     for (;;) {
9226         yyboardindex = boardIndex;
9227         moveType = (ChessMove) Myylex();
9228         switch (moveType) {
9229           case IllegalMove:             /* maybe suicide chess, etc. */
9230   if (appData.debugMode) {
9231     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9232     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9233     setbuf(debugFP, NULL);
9234   }
9235           case WhitePromotion:
9236           case BlackPromotion:
9237           case WhiteNonPromotion:
9238           case BlackNonPromotion:
9239           case NormalMove:
9240           case WhiteCapturesEnPassant:
9241           case BlackCapturesEnPassant:
9242           case WhiteKingSideCastle:
9243           case WhiteQueenSideCastle:
9244           case BlackKingSideCastle:
9245           case BlackQueenSideCastle:
9246           case WhiteKingSideCastleWild:
9247           case WhiteQueenSideCastleWild:
9248           case BlackKingSideCastleWild:
9249           case BlackQueenSideCastleWild:
9250           /* PUSH Fabien */
9251           case WhiteHSideCastleFR:
9252           case WhiteASideCastleFR:
9253           case BlackHSideCastleFR:
9254           case BlackASideCastleFR:
9255           /* POP Fabien */
9256             fromX = currentMoveString[0] - AAA;
9257             fromY = currentMoveString[1] - ONE;
9258             toX = currentMoveString[2] - AAA;
9259             toY = currentMoveString[3] - ONE;
9260             promoChar = currentMoveString[4];
9261             break;
9262           case WhiteDrop:
9263           case BlackDrop:
9264             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9265             fromX = moveType == WhiteDrop ?
9266               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9267             (int) CharToPiece(ToLower(currentMoveString[0]));
9268             fromY = DROP_RANK;
9269             toX = currentMoveString[2] - AAA;
9270             toY = currentMoveString[3] - ONE;
9271             promoChar = NULLCHAR;
9272             break;
9273           case AmbiguousMove:
9274             /* bug? */
9275             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9276   if (appData.debugMode) {
9277     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9278     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9279     setbuf(debugFP, NULL);
9280   }
9281             DisplayError(buf, 0);
9282             return;
9283           case ImpossibleMove:
9284             /* bug? */
9285             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9286   if (appData.debugMode) {
9287     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9288     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9289     setbuf(debugFP, NULL);
9290   }
9291             DisplayError(buf, 0);
9292             return;
9293           case EndOfFile:
9294             if (boardIndex < backwardMostMove) {
9295                 /* Oops, gap.  How did that happen? */
9296                 DisplayError(_("Gap in move list"), 0);
9297                 return;
9298             }
9299             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9300             if (boardIndex > forwardMostMove) {
9301                 forwardMostMove = boardIndex;
9302             }
9303             return;
9304           case ElapsedTime:
9305             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9306                 strcat(parseList[boardIndex-1], " ");
9307                 strcat(parseList[boardIndex-1], yy_text);
9308             }
9309             continue;
9310           case Comment:
9311           case PGNTag:
9312           case NAG:
9313           default:
9314             /* ignore */
9315             continue;
9316           case WhiteWins:
9317           case BlackWins:
9318           case GameIsDrawn:
9319           case GameUnfinished:
9320             if (gameMode == IcsExamining) {
9321                 if (boardIndex < backwardMostMove) {
9322                     /* Oops, gap.  How did that happen? */
9323                     return;
9324                 }
9325                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9326                 return;
9327             }
9328             gameInfo.result = moveType;
9329             p = strchr(yy_text, '{');
9330             if (p == NULL) p = strchr(yy_text, '(');
9331             if (p == NULL) {
9332                 p = yy_text;
9333                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9334             } else {
9335                 q = strchr(p, *p == '{' ? '}' : ')');
9336                 if (q != NULL) *q = NULLCHAR;
9337                 p++;
9338             }
9339             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9340             gameInfo.resultDetails = StrSave(p);
9341             continue;
9342         }
9343         if (boardIndex >= forwardMostMove &&
9344             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9345             backwardMostMove = blackPlaysFirst ? 1 : 0;
9346             return;
9347         }
9348         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9349                                  fromY, fromX, toY, toX, promoChar,
9350                                  parseList[boardIndex]);
9351         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9352         /* currentMoveString is set as a side-effect of yylex */
9353         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9354         strcat(moveList[boardIndex], "\n");
9355         boardIndex++;
9356         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9357         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9358           case MT_NONE:
9359           case MT_STALEMATE:
9360           default:
9361             break;
9362           case MT_CHECK:
9363             if(gameInfo.variant != VariantShogi)
9364                 strcat(parseList[boardIndex - 1], "+");
9365             break;
9366           case MT_CHECKMATE:
9367           case MT_STAINMATE:
9368             strcat(parseList[boardIndex - 1], "#");
9369             break;
9370         }
9371     }
9372 }
9373
9374
9375 /* Apply a move to the given board  */
9376 void
9377 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9378 {
9379   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9380   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9381
9382     /* [HGM] compute & store e.p. status and castling rights for new position */
9383     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9384
9385       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9386       oldEP = (signed char)board[EP_STATUS];
9387       board[EP_STATUS] = EP_NONE;
9388
9389   if (fromY == DROP_RANK) {
9390         /* must be first */
9391         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9392             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9393             return;
9394         }
9395         piece = board[toY][toX] = (ChessSquare) fromX;
9396   } else {
9397       int i;
9398
9399       if( board[toY][toX] != EmptySquare )
9400            board[EP_STATUS] = EP_CAPTURE;
9401
9402       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9403            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9404                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9405       } else
9406       if( board[fromY][fromX] == WhitePawn ) {
9407            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9408                board[EP_STATUS] = EP_PAWN_MOVE;
9409            if( toY-fromY==2) {
9410                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9411                         gameInfo.variant != VariantBerolina || toX < fromX)
9412                       board[EP_STATUS] = toX | berolina;
9413                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9414                         gameInfo.variant != VariantBerolina || toX > fromX)
9415                       board[EP_STATUS] = toX;
9416            }
9417       } else
9418       if( board[fromY][fromX] == BlackPawn ) {
9419            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9420                board[EP_STATUS] = EP_PAWN_MOVE;
9421            if( toY-fromY== -2) {
9422                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9423                         gameInfo.variant != VariantBerolina || toX < fromX)
9424                       board[EP_STATUS] = toX | berolina;
9425                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9426                         gameInfo.variant != VariantBerolina || toX > fromX)
9427                       board[EP_STATUS] = toX;
9428            }
9429        }
9430
9431        for(i=0; i<nrCastlingRights; i++) {
9432            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9433               board[CASTLING][i] == toX   && castlingRank[i] == toY
9434              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9435        }
9436
9437        if(gameInfo.variant == VariantSChess) { // update virginity
9438            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9439            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9440            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9441            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9442        }
9443
9444      if (fromX == toX && fromY == toY) return;
9445
9446      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9447      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9448      if(gameInfo.variant == VariantKnightmate)
9449          king += (int) WhiteUnicorn - (int) WhiteKing;
9450
9451     /* Code added by Tord: */
9452     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9453     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9454         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9455       board[fromY][fromX] = EmptySquare;
9456       board[toY][toX] = EmptySquare;
9457       if((toX > fromX) != (piece == WhiteRook)) {
9458         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9459       } else {
9460         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9461       }
9462     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9463                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9464       board[fromY][fromX] = EmptySquare;
9465       board[toY][toX] = EmptySquare;
9466       if((toX > fromX) != (piece == BlackRook)) {
9467         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9468       } else {
9469         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9470       }
9471     /* End of code added by Tord */
9472
9473     } else if (board[fromY][fromX] == king
9474         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9475         && toY == fromY && toX > fromX+1) {
9476         board[fromY][fromX] = EmptySquare;
9477         board[toY][toX] = king;
9478         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9479         board[fromY][BOARD_RGHT-1] = EmptySquare;
9480     } else if (board[fromY][fromX] == king
9481         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9482                && toY == fromY && toX < fromX-1) {
9483         board[fromY][fromX] = EmptySquare;
9484         board[toY][toX] = king;
9485         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9486         board[fromY][BOARD_LEFT] = EmptySquare;
9487     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9488                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9489                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9490                ) {
9491         /* white pawn promotion */
9492         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9493         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9494             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9495         board[fromY][fromX] = EmptySquare;
9496     } else if ((fromY >= BOARD_HEIGHT>>1)
9497                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9498                && (toX != fromX)
9499                && gameInfo.variant != VariantXiangqi
9500                && gameInfo.variant != VariantBerolina
9501                && (board[fromY][fromX] == WhitePawn)
9502                && (board[toY][toX] == EmptySquare)) {
9503         board[fromY][fromX] = EmptySquare;
9504         board[toY][toX] = WhitePawn;
9505         captured = board[toY - 1][toX];
9506         board[toY - 1][toX] = EmptySquare;
9507     } else if ((fromY == BOARD_HEIGHT-4)
9508                && (toX == fromX)
9509                && gameInfo.variant == VariantBerolina
9510                && (board[fromY][fromX] == WhitePawn)
9511                && (board[toY][toX] == EmptySquare)) {
9512         board[fromY][fromX] = EmptySquare;
9513         board[toY][toX] = WhitePawn;
9514         if(oldEP & EP_BEROLIN_A) {
9515                 captured = board[fromY][fromX-1];
9516                 board[fromY][fromX-1] = EmptySquare;
9517         }else{  captured = board[fromY][fromX+1];
9518                 board[fromY][fromX+1] = EmptySquare;
9519         }
9520     } else if (board[fromY][fromX] == king
9521         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9522                && toY == fromY && toX > fromX+1) {
9523         board[fromY][fromX] = EmptySquare;
9524         board[toY][toX] = king;
9525         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9526         board[fromY][BOARD_RGHT-1] = EmptySquare;
9527     } else if (board[fromY][fromX] == king
9528         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9529                && toY == fromY && toX < fromX-1) {
9530         board[fromY][fromX] = EmptySquare;
9531         board[toY][toX] = king;
9532         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9533         board[fromY][BOARD_LEFT] = EmptySquare;
9534     } else if (fromY == 7 && fromX == 3
9535                && board[fromY][fromX] == BlackKing
9536                && toY == 7 && toX == 5) {
9537         board[fromY][fromX] = EmptySquare;
9538         board[toY][toX] = BlackKing;
9539         board[fromY][7] = EmptySquare;
9540         board[toY][4] = BlackRook;
9541     } else if (fromY == 7 && fromX == 3
9542                && board[fromY][fromX] == BlackKing
9543                && toY == 7 && toX == 1) {
9544         board[fromY][fromX] = EmptySquare;
9545         board[toY][toX] = BlackKing;
9546         board[fromY][0] = EmptySquare;
9547         board[toY][2] = BlackRook;
9548     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9549                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9550                && toY < promoRank && promoChar
9551                ) {
9552         /* black pawn promotion */
9553         board[toY][toX] = CharToPiece(ToLower(promoChar));
9554         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9555             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9556         board[fromY][fromX] = EmptySquare;
9557     } else if ((fromY < BOARD_HEIGHT>>1)
9558                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9559                && (toX != fromX)
9560                && gameInfo.variant != VariantXiangqi
9561                && gameInfo.variant != VariantBerolina
9562                && (board[fromY][fromX] == BlackPawn)
9563                && (board[toY][toX] == EmptySquare)) {
9564         board[fromY][fromX] = EmptySquare;
9565         board[toY][toX] = BlackPawn;
9566         captured = board[toY + 1][toX];
9567         board[toY + 1][toX] = EmptySquare;
9568     } else if ((fromY == 3)
9569                && (toX == fromX)
9570                && gameInfo.variant == VariantBerolina
9571                && (board[fromY][fromX] == BlackPawn)
9572                && (board[toY][toX] == EmptySquare)) {
9573         board[fromY][fromX] = EmptySquare;
9574         board[toY][toX] = BlackPawn;
9575         if(oldEP & EP_BEROLIN_A) {
9576                 captured = board[fromY][fromX-1];
9577                 board[fromY][fromX-1] = EmptySquare;
9578         }else{  captured = board[fromY][fromX+1];
9579                 board[fromY][fromX+1] = EmptySquare;
9580         }
9581     } else {
9582         board[toY][toX] = board[fromY][fromX];
9583         board[fromY][fromX] = EmptySquare;
9584     }
9585   }
9586
9587     if (gameInfo.holdingsWidth != 0) {
9588
9589       /* !!A lot more code needs to be written to support holdings  */
9590       /* [HGM] OK, so I have written it. Holdings are stored in the */
9591       /* penultimate board files, so they are automaticlly stored   */
9592       /* in the game history.                                       */
9593       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9594                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9595         /* Delete from holdings, by decreasing count */
9596         /* and erasing image if necessary            */
9597         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9598         if(p < (int) BlackPawn) { /* white drop */
9599              p -= (int)WhitePawn;
9600                  p = PieceToNumber((ChessSquare)p);
9601              if(p >= gameInfo.holdingsSize) p = 0;
9602              if(--board[p][BOARD_WIDTH-2] <= 0)
9603                   board[p][BOARD_WIDTH-1] = EmptySquare;
9604              if((int)board[p][BOARD_WIDTH-2] < 0)
9605                         board[p][BOARD_WIDTH-2] = 0;
9606         } else {                  /* black drop */
9607              p -= (int)BlackPawn;
9608                  p = PieceToNumber((ChessSquare)p);
9609              if(p >= gameInfo.holdingsSize) p = 0;
9610              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9611                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9612              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9613                         board[BOARD_HEIGHT-1-p][1] = 0;
9614         }
9615       }
9616       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9617           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9618         /* [HGM] holdings: Add to holdings, if holdings exist */
9619         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9620                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9621                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9622         }
9623         p = (int) captured;
9624         if (p >= (int) BlackPawn) {
9625           p -= (int)BlackPawn;
9626           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9627                   /* in Shogi restore piece to its original  first */
9628                   captured = (ChessSquare) (DEMOTED captured);
9629                   p = DEMOTED p;
9630           }
9631           p = PieceToNumber((ChessSquare)p);
9632           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9633           board[p][BOARD_WIDTH-2]++;
9634           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9635         } else {
9636           p -= (int)WhitePawn;
9637           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9638                   captured = (ChessSquare) (DEMOTED captured);
9639                   p = DEMOTED p;
9640           }
9641           p = PieceToNumber((ChessSquare)p);
9642           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9643           board[BOARD_HEIGHT-1-p][1]++;
9644           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9645         }
9646       }
9647     } else if (gameInfo.variant == VariantAtomic) {
9648       if (captured != EmptySquare) {
9649         int y, x;
9650         for (y = toY-1; y <= toY+1; y++) {
9651           for (x = toX-1; x <= toX+1; x++) {
9652             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9653                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9654               board[y][x] = EmptySquare;
9655             }
9656           }
9657         }
9658         board[toY][toX] = EmptySquare;
9659       }
9660     }
9661     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9662         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9663     } else
9664     if(promoChar == '+') {
9665         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9666         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9667     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9668         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9669         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9670            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9671         board[toY][toX] = newPiece;
9672     }
9673     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9674                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9675         // [HGM] superchess: take promotion piece out of holdings
9676         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9677         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9678             if(!--board[k][BOARD_WIDTH-2])
9679                 board[k][BOARD_WIDTH-1] = EmptySquare;
9680         } else {
9681             if(!--board[BOARD_HEIGHT-1-k][1])
9682                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9683         }
9684     }
9685
9686 }
9687
9688 /* Updates forwardMostMove */
9689 void
9690 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9691 {
9692 //    forwardMostMove++; // [HGM] bare: moved downstream
9693
9694     (void) CoordsToAlgebraic(boards[forwardMostMove],
9695                              PosFlags(forwardMostMove),
9696                              fromY, fromX, toY, toX, promoChar,
9697                              parseList[forwardMostMove]);
9698
9699     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9700         int timeLeft; static int lastLoadFlag=0; int king, piece;
9701         piece = boards[forwardMostMove][fromY][fromX];
9702         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9703         if(gameInfo.variant == VariantKnightmate)
9704             king += (int) WhiteUnicorn - (int) WhiteKing;
9705         if(forwardMostMove == 0) {
9706             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9707                 fprintf(serverMoves, "%s;", UserName());
9708             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9709                 fprintf(serverMoves, "%s;", second.tidy);
9710             fprintf(serverMoves, "%s;", first.tidy);
9711             if(gameMode == MachinePlaysWhite)
9712                 fprintf(serverMoves, "%s;", UserName());
9713             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9714                 fprintf(serverMoves, "%s;", second.tidy);
9715         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9716         lastLoadFlag = loadFlag;
9717         // print base move
9718         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9719         // print castling suffix
9720         if( toY == fromY && piece == king ) {
9721             if(toX-fromX > 1)
9722                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9723             if(fromX-toX >1)
9724                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9725         }
9726         // e.p. suffix
9727         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9728              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9729              boards[forwardMostMove][toY][toX] == EmptySquare
9730              && fromX != toX && fromY != toY)
9731                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9732         // promotion suffix
9733         if(promoChar != NULLCHAR) {
9734             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9735                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9736                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9737             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9738         }
9739         if(!loadFlag) {
9740                 char buf[MOVE_LEN*2], *p; int len;
9741             fprintf(serverMoves, "/%d/%d",
9742                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9743             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9744             else                      timeLeft = blackTimeRemaining/1000;
9745             fprintf(serverMoves, "/%d", timeLeft);
9746                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9747                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9748                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9749                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9750             fprintf(serverMoves, "/%s", buf);
9751         }
9752         fflush(serverMoves);
9753     }
9754
9755     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9756         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9757       return;
9758     }
9759     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9760     if (commentList[forwardMostMove+1] != NULL) {
9761         free(commentList[forwardMostMove+1]);
9762         commentList[forwardMostMove+1] = NULL;
9763     }
9764     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9765     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9766     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9767     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9768     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9769     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9770     adjustedClock = FALSE;
9771     gameInfo.result = GameUnfinished;
9772     if (gameInfo.resultDetails != NULL) {
9773         free(gameInfo.resultDetails);
9774         gameInfo.resultDetails = NULL;
9775     }
9776     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9777                               moveList[forwardMostMove - 1]);
9778     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9779       case MT_NONE:
9780       case MT_STALEMATE:
9781       default:
9782         break;
9783       case MT_CHECK:
9784         if(gameInfo.variant != VariantShogi)
9785             strcat(parseList[forwardMostMove - 1], "+");
9786         break;
9787       case MT_CHECKMATE:
9788       case MT_STAINMATE:
9789         strcat(parseList[forwardMostMove - 1], "#");
9790         break;
9791     }
9792
9793 }
9794
9795 /* Updates currentMove if not pausing */
9796 void
9797 ShowMove (int fromX, int fromY, int toX, int toY)
9798 {
9799     int instant = (gameMode == PlayFromGameFile) ?
9800         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9801     if(appData.noGUI) return;
9802     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9803         if (!instant) {
9804             if (forwardMostMove == currentMove + 1) {
9805                 AnimateMove(boards[forwardMostMove - 1],
9806                             fromX, fromY, toX, toY);
9807             }
9808         }
9809         currentMove = forwardMostMove;
9810     }
9811
9812     if (instant) return;
9813
9814     DisplayMove(currentMove - 1);
9815     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9816             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9817                 SetHighlights(fromX, fromY, toX, toY);
9818             }
9819     }
9820     DrawPosition(FALSE, boards[currentMove]);
9821     DisplayBothClocks();
9822     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9823 }
9824
9825 void
9826 SendEgtPath (ChessProgramState *cps)
9827 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9828         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9829
9830         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9831
9832         while(*p) {
9833             char c, *q = name+1, *r, *s;
9834
9835             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9836             while(*p && *p != ',') *q++ = *p++;
9837             *q++ = ':'; *q = 0;
9838             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9839                 strcmp(name, ",nalimov:") == 0 ) {
9840                 // take nalimov path from the menu-changeable option first, if it is defined
9841               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9842                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9843             } else
9844             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9845                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9846                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9847                 s = r = StrStr(s, ":") + 1; // beginning of path info
9848                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9849                 c = *r; *r = 0;             // temporarily null-terminate path info
9850                     *--q = 0;               // strip of trailig ':' from name
9851                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9852                 *r = c;
9853                 SendToProgram(buf,cps);     // send egtbpath command for this format
9854             }
9855             if(*p == ',') p++; // read away comma to position for next format name
9856         }
9857 }
9858
9859 static int
9860 NonStandardBoardSize ()
9861 {
9862       /* [HGM] Awkward testing. Should really be a table */
9863       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9864       if( gameInfo.variant == VariantXiangqi )
9865            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9866       if( gameInfo.variant == VariantShogi )
9867            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9868       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9869            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9870       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9871           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9872            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9873       if( gameInfo.variant == VariantCourier )
9874            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9875       if( gameInfo.variant == VariantSuper )
9876            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9877       if( gameInfo.variant == VariantGreat )
9878            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9879       if( gameInfo.variant == VariantSChess )
9880            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9881       if( gameInfo.variant == VariantGrand )
9882            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9883       return overruled;
9884 }
9885
9886 void
9887 InitChessProgram (ChessProgramState *cps, int setup)
9888 /* setup needed to setup FRC opening position */
9889 {
9890     char buf[MSG_SIZ], b[MSG_SIZ];
9891     if (appData.noChessProgram) return;
9892     hintRequested = FALSE;
9893     bookRequested = FALSE;
9894
9895     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9896     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9897     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9898     if(cps->memSize) { /* [HGM] memory */
9899       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9900         SendToProgram(buf, cps);
9901     }
9902     SendEgtPath(cps); /* [HGM] EGT */
9903     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9904       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9905         SendToProgram(buf, cps);
9906     }
9907
9908     SendToProgram(cps->initString, cps);
9909     if (gameInfo.variant != VariantNormal &&
9910         gameInfo.variant != VariantLoadable
9911         /* [HGM] also send variant if board size non-standard */
9912         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9913                                             ) {
9914       char *v = VariantName(gameInfo.variant);
9915       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9916         /* [HGM] in protocol 1 we have to assume all variants valid */
9917         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9918         DisplayFatalError(buf, 0, 1);
9919         return;
9920       }
9921
9922       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
9923         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9924                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9925            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9926            if(StrStr(cps->variants, b) == NULL) {
9927                // specific sized variant not known, check if general sizing allowed
9928                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9929                    if(StrStr(cps->variants, "boardsize") == NULL) {
9930                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9931                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9932                        DisplayFatalError(buf, 0, 1);
9933                        return;
9934                    }
9935                    /* [HGM] here we really should compare with the maximum supported board size */
9936                }
9937            }
9938       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9939       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9940       SendToProgram(buf, cps);
9941     }
9942     currentlyInitializedVariant = gameInfo.variant;
9943
9944     /* [HGM] send opening position in FRC to first engine */
9945     if(setup) {
9946           SendToProgram("force\n", cps);
9947           SendBoard(cps, 0);
9948           /* engine is now in force mode! Set flag to wake it up after first move. */
9949           setboardSpoiledMachineBlack = 1;
9950     }
9951
9952     if (cps->sendICS) {
9953       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9954       SendToProgram(buf, cps);
9955     }
9956     cps->maybeThinking = FALSE;
9957     cps->offeredDraw = 0;
9958     if (!appData.icsActive) {
9959         SendTimeControl(cps, movesPerSession, timeControl,
9960                         timeIncrement, appData.searchDepth,
9961                         searchTime);
9962     }
9963     if (appData.showThinking
9964         // [HGM] thinking: four options require thinking output to be sent
9965         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9966                                 ) {
9967         SendToProgram("post\n", cps);
9968     }
9969     SendToProgram("hard\n", cps);
9970     if (!appData.ponderNextMove) {
9971         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9972            it without being sure what state we are in first.  "hard"
9973            is not a toggle, so that one is OK.
9974          */
9975         SendToProgram("easy\n", cps);
9976     }
9977     if (cps->usePing) {
9978       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9979       SendToProgram(buf, cps);
9980     }
9981     cps->initDone = TRUE;
9982     ClearEngineOutputPane(cps == &second);
9983 }
9984
9985
9986 void
9987 ResendOptions (ChessProgramState *cps)
9988 { // send the stored value of the options
9989   int i;
9990   char buf[MSG_SIZ];
9991   Option *opt = cps->option;
9992   for(i=0; i<cps->nrOptions; i++, opt++) {
9993       switch(opt->type) {
9994         case Spin:
9995         case Slider:
9996         case CheckBox:
9997             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9998           break;
9999         case ComboBox:
10000           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10001           break;
10002         default:
10003             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10004           break;
10005         case Button:
10006         case SaveButton:
10007           continue;
10008       }
10009       SendToProgram(buf, cps);
10010   }
10011 }
10012
10013 void
10014 StartChessProgram (ChessProgramState *cps)
10015 {
10016     char buf[MSG_SIZ];
10017     int err;
10018
10019     if (appData.noChessProgram) return;
10020     cps->initDone = FALSE;
10021
10022     if (strcmp(cps->host, "localhost") == 0) {
10023         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10024     } else if (*appData.remoteShell == NULLCHAR) {
10025         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10026     } else {
10027         if (*appData.remoteUser == NULLCHAR) {
10028           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10029                     cps->program);
10030         } else {
10031           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10032                     cps->host, appData.remoteUser, cps->program);
10033         }
10034         err = StartChildProcess(buf, "", &cps->pr);
10035     }
10036
10037     if (err != 0) {
10038       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10039         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10040         if(cps != &first) return;
10041         appData.noChessProgram = TRUE;
10042         ThawUI();
10043         SetNCPMode();
10044 //      DisplayFatalError(buf, err, 1);
10045 //      cps->pr = NoProc;
10046 //      cps->isr = NULL;
10047         return;
10048     }
10049
10050     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10051     if (cps->protocolVersion > 1) {
10052       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10053       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10054         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10055         cps->comboCnt = 0;  //                and values of combo boxes
10056       }
10057       SendToProgram(buf, cps);
10058       if(cps->reload) ResendOptions(cps);
10059     } else {
10060       SendToProgram("xboard\n", cps);
10061     }
10062 }
10063
10064 void
10065 TwoMachinesEventIfReady P((void))
10066 {
10067   static int curMess = 0;
10068   if (first.lastPing != first.lastPong) {
10069     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10070     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10071     return;
10072   }
10073   if (second.lastPing != second.lastPong) {
10074     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10075     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10076     return;
10077   }
10078   DisplayMessage("", ""); curMess = 0;
10079   TwoMachinesEvent();
10080 }
10081
10082 char *
10083 MakeName (char *template)
10084 {
10085     time_t clock;
10086     struct tm *tm;
10087     static char buf[MSG_SIZ];
10088     char *p = buf;
10089     int i;
10090
10091     clock = time((time_t *)NULL);
10092     tm = localtime(&clock);
10093
10094     while(*p++ = *template++) if(p[-1] == '%') {
10095         switch(*template++) {
10096           case 0:   *p = 0; return buf;
10097           case 'Y': i = tm->tm_year+1900; break;
10098           case 'y': i = tm->tm_year-100; break;
10099           case 'M': i = tm->tm_mon+1; break;
10100           case 'd': i = tm->tm_mday; break;
10101           case 'h': i = tm->tm_hour; break;
10102           case 'm': i = tm->tm_min; break;
10103           case 's': i = tm->tm_sec; break;
10104           default:  i = 0;
10105         }
10106         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10107     }
10108     return buf;
10109 }
10110
10111 int
10112 CountPlayers (char *p)
10113 {
10114     int n = 0;
10115     while(p = strchr(p, '\n')) p++, n++; // count participants
10116     return n;
10117 }
10118
10119 FILE *
10120 WriteTourneyFile (char *results, FILE *f)
10121 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10122     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10123     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10124         // create a file with tournament description
10125         fprintf(f, "-participants {%s}\n", appData.participants);
10126         fprintf(f, "-seedBase %d\n", appData.seedBase);
10127         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10128         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10129         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10130         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10131         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10132         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10133         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10134         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10135         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10136         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10137         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10138         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10139         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10140         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10141         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10142         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10143         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10144         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10145         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10146         fprintf(f, "-smpCores %d\n", appData.smpCores);
10147         if(searchTime > 0)
10148                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10149         else {
10150                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10151                 fprintf(f, "-tc %s\n", appData.timeControl);
10152                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10153         }
10154         fprintf(f, "-results \"%s\"\n", results);
10155     }
10156     return f;
10157 }
10158
10159 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10160
10161 void
10162 Substitute (char *participants, int expunge)
10163 {
10164     int i, changed, changes=0, nPlayers=0;
10165     char *p, *q, *r, buf[MSG_SIZ];
10166     if(participants == NULL) return;
10167     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10168     r = p = participants; q = appData.participants;
10169     while(*p && *p == *q) {
10170         if(*p == '\n') r = p+1, nPlayers++;
10171         p++; q++;
10172     }
10173     if(*p) { // difference
10174         while(*p && *p++ != '\n');
10175         while(*q && *q++ != '\n');
10176       changed = nPlayers;
10177         changes = 1 + (strcmp(p, q) != 0);
10178     }
10179     if(changes == 1) { // a single engine mnemonic was changed
10180         q = r; while(*q) nPlayers += (*q++ == '\n');
10181         p = buf; while(*r && (*p = *r++) != '\n') p++;
10182         *p = NULLCHAR;
10183         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10184         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10185         if(mnemonic[i]) { // The substitute is valid
10186             FILE *f;
10187             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10188                 flock(fileno(f), LOCK_EX);
10189                 ParseArgsFromFile(f);
10190                 fseek(f, 0, SEEK_SET);
10191                 FREE(appData.participants); appData.participants = participants;
10192                 if(expunge) { // erase results of replaced engine
10193                     int len = strlen(appData.results), w, b, dummy;
10194                     for(i=0; i<len; i++) {
10195                         Pairing(i, nPlayers, &w, &b, &dummy);
10196                         if((w == changed || b == changed) && appData.results[i] == '*') {
10197                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10198                             fclose(f);
10199                             return;
10200                         }
10201                     }
10202                     for(i=0; i<len; i++) {
10203                         Pairing(i, nPlayers, &w, &b, &dummy);
10204                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10205                     }
10206                 }
10207                 WriteTourneyFile(appData.results, f);
10208                 fclose(f); // release lock
10209                 return;
10210             }
10211         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10212     }
10213     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10214     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10215     free(participants);
10216     return;
10217 }
10218
10219 int
10220 CheckPlayers (char *participants)
10221 {
10222         int i;
10223         char buf[MSG_SIZ], *p;
10224         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10225         while(p = strchr(participants, '\n')) {
10226             *p = NULLCHAR;
10227             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10228             if(!mnemonic[i]) {
10229                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10230                 *p = '\n';
10231                 DisplayError(buf, 0);
10232                 return 1;
10233             }
10234             *p = '\n';
10235             participants = p + 1;
10236         }
10237         return 0;
10238 }
10239
10240 int
10241 CreateTourney (char *name)
10242 {
10243         FILE *f;
10244         if(matchMode && strcmp(name, appData.tourneyFile)) {
10245              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10246         }
10247         if(name[0] == NULLCHAR) {
10248             if(appData.participants[0])
10249                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10250             return 0;
10251         }
10252         f = fopen(name, "r");
10253         if(f) { // file exists
10254             ASSIGN(appData.tourneyFile, name);
10255             ParseArgsFromFile(f); // parse it
10256         } else {
10257             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10258             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10259                 DisplayError(_("Not enough participants"), 0);
10260                 return 0;
10261             }
10262             if(CheckPlayers(appData.participants)) return 0;
10263             ASSIGN(appData.tourneyFile, name);
10264             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10265             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10266         }
10267         fclose(f);
10268         appData.noChessProgram = FALSE;
10269         appData.clockMode = TRUE;
10270         SetGNUMode();
10271         return 1;
10272 }
10273
10274 int
10275 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10276 {
10277     char buf[MSG_SIZ], *p, *q;
10278     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10279     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10280     skip = !all && group[0]; // if group requested, we start in skip mode
10281     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10282         p = names; q = buf; header = 0;
10283         while(*p && *p != '\n') *q++ = *p++;
10284         *q = 0;
10285         if(*p == '\n') p++;
10286         if(buf[0] == '#') {
10287             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10288             depth++; // we must be entering a new group
10289             if(all) continue; // suppress printing group headers when complete list requested
10290             header = 1;
10291             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10292         }
10293         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10294         if(engineList[i]) free(engineList[i]);
10295         engineList[i] = strdup(buf);
10296         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10297         if(engineMnemonic[i]) free(engineMnemonic[i]);
10298         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10299             strcat(buf, " (");
10300             sscanf(q + 8, "%s", buf + strlen(buf));
10301             strcat(buf, ")");
10302         }
10303         engineMnemonic[i] = strdup(buf);
10304         i++;
10305     }
10306     engineList[i] = engineMnemonic[i] = NULL;
10307     return i;
10308 }
10309
10310 // following implemented as macro to avoid type limitations
10311 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10312
10313 void
10314 SwapEngines (int n)
10315 {   // swap settings for first engine and other engine (so far only some selected options)
10316     int h;
10317     char *p;
10318     if(n == 0) return;
10319     SWAP(directory, p)
10320     SWAP(chessProgram, p)
10321     SWAP(isUCI, h)
10322     SWAP(hasOwnBookUCI, h)
10323     SWAP(protocolVersion, h)
10324     SWAP(reuse, h)
10325     SWAP(scoreIsAbsolute, h)
10326     SWAP(timeOdds, h)
10327     SWAP(logo, p)
10328     SWAP(pgnName, p)
10329     SWAP(pvSAN, h)
10330     SWAP(engOptions, p)
10331     SWAP(engInitString, p)
10332     SWAP(computerString, p)
10333     SWAP(features, p)
10334     SWAP(fenOverride, p)
10335     SWAP(NPS, h)
10336     SWAP(accumulateTC, h)
10337     SWAP(host, p)
10338 }
10339
10340 int
10341 GetEngineLine (char *s, int n)
10342 {
10343     int i;
10344     char buf[MSG_SIZ];
10345     extern char *icsNames;
10346     if(!s || !*s) return 0;
10347     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10348     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10349     if(!mnemonic[i]) return 0;
10350     if(n == 11) return 1; // just testing if there was a match
10351     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10352     if(n == 1) SwapEngines(n);
10353     ParseArgsFromString(buf);
10354     if(n == 1) SwapEngines(n);
10355     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10356         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10357         ParseArgsFromString(buf);
10358     }
10359     return 1;
10360 }
10361
10362 int
10363 SetPlayer (int player, char *p)
10364 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10365     int i;
10366     char buf[MSG_SIZ], *engineName;
10367     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10368     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10369     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10370     if(mnemonic[i]) {
10371         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10372         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10373         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10374         ParseArgsFromString(buf);
10375     } else { // no engine with this nickname is installed!
10376         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10377         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10378         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10379         ModeHighlight();
10380         DisplayError(buf, 0);
10381         return 0;
10382     }
10383     free(engineName);
10384     return i;
10385 }
10386
10387 char *recentEngines;
10388
10389 void
10390 RecentEngineEvent (int nr)
10391 {
10392     int n;
10393 //    SwapEngines(1); // bump first to second
10394 //    ReplaceEngine(&second, 1); // and load it there
10395     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10396     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10397     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10398         ReplaceEngine(&first, 0);
10399         FloatToFront(&appData.recentEngineList, command[n]);
10400     }
10401 }
10402
10403 int
10404 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10405 {   // determine players from game number
10406     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10407
10408     if(appData.tourneyType == 0) {
10409         roundsPerCycle = (nPlayers - 1) | 1;
10410         pairingsPerRound = nPlayers / 2;
10411     } else if(appData.tourneyType > 0) {
10412         roundsPerCycle = nPlayers - appData.tourneyType;
10413         pairingsPerRound = appData.tourneyType;
10414     }
10415     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10416     gamesPerCycle = gamesPerRound * roundsPerCycle;
10417     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10418     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10419     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10420     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10421     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10422     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10423
10424     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10425     if(appData.roundSync) *syncInterval = gamesPerRound;
10426
10427     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10428
10429     if(appData.tourneyType == 0) {
10430         if(curPairing == (nPlayers-1)/2 ) {
10431             *whitePlayer = curRound;
10432             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10433         } else {
10434             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10435             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10436             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10437             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10438         }
10439     } else if(appData.tourneyType > 1) {
10440         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10441         *whitePlayer = curRound + appData.tourneyType;
10442     } else if(appData.tourneyType > 0) {
10443         *whitePlayer = curPairing;
10444         *blackPlayer = curRound + appData.tourneyType;
10445     }
10446
10447     // take care of white/black alternation per round.
10448     // For cycles and games this is already taken care of by default, derived from matchGame!
10449     return curRound & 1;
10450 }
10451
10452 int
10453 NextTourneyGame (int nr, int *swapColors)
10454 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10455     char *p, *q;
10456     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10457     FILE *tf;
10458     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10459     tf = fopen(appData.tourneyFile, "r");
10460     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10461     ParseArgsFromFile(tf); fclose(tf);
10462     InitTimeControls(); // TC might be altered from tourney file
10463
10464     nPlayers = CountPlayers(appData.participants); // count participants
10465     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10466     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10467
10468     if(syncInterval) {
10469         p = q = appData.results;
10470         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10471         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10472             DisplayMessage(_("Waiting for other game(s)"),"");
10473             waitingForGame = TRUE;
10474             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10475             return 0;
10476         }
10477         waitingForGame = FALSE;
10478     }
10479
10480     if(appData.tourneyType < 0) {
10481         if(nr>=0 && !pairingReceived) {
10482             char buf[1<<16];
10483             if(pairing.pr == NoProc) {
10484                 if(!appData.pairingEngine[0]) {
10485                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10486                     return 0;
10487                 }
10488                 StartChessProgram(&pairing); // starts the pairing engine
10489             }
10490             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10491             SendToProgram(buf, &pairing);
10492             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10493             SendToProgram(buf, &pairing);
10494             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10495         }
10496         pairingReceived = 0;                              // ... so we continue here
10497         *swapColors = 0;
10498         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10499         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10500         matchGame = 1; roundNr = nr / syncInterval + 1;
10501     }
10502
10503     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10504
10505     // redefine engines, engine dir, etc.
10506     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10507     if(first.pr == NoProc) {
10508       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10509       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10510     }
10511     if(second.pr == NoProc) {
10512       SwapEngines(1);
10513       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10514       SwapEngines(1);         // and make that valid for second engine by swapping
10515       InitEngine(&second, 1);
10516     }
10517     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10518     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10519     return OK;
10520 }
10521
10522 void
10523 NextMatchGame ()
10524 {   // performs game initialization that does not invoke engines, and then tries to start the game
10525     int res, firstWhite, swapColors = 0;
10526     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10527     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
10528         char buf[MSG_SIZ];
10529         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10530         if(strcmp(buf, currentDebugFile)) { // name has changed
10531             FILE *f = fopen(buf, "w");
10532             if(f) { // if opening the new file failed, just keep using the old one
10533                 ASSIGN(currentDebugFile, buf);
10534                 fclose(debugFP);
10535                 debugFP = f;
10536             }
10537             if(appData.serverFileName) {
10538                 if(serverFP) fclose(serverFP);
10539                 serverFP = fopen(appData.serverFileName, "w");
10540                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10541                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10542             }
10543         }
10544     }
10545     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10546     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10547     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10548     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10549     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10550     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10551     Reset(FALSE, first.pr != NoProc);
10552     res = LoadGameOrPosition(matchGame); // setup game
10553     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10554     if(!res) return; // abort when bad game/pos file
10555     TwoMachinesEvent();
10556 }
10557
10558 void
10559 UserAdjudicationEvent (int result)
10560 {
10561     ChessMove gameResult = GameIsDrawn;
10562
10563     if( result > 0 ) {
10564         gameResult = WhiteWins;
10565     }
10566     else if( result < 0 ) {
10567         gameResult = BlackWins;
10568     }
10569
10570     if( gameMode == TwoMachinesPlay ) {
10571         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10572     }
10573 }
10574
10575
10576 // [HGM] save: calculate checksum of game to make games easily identifiable
10577 int
10578 StringCheckSum (char *s)
10579 {
10580         int i = 0;
10581         if(s==NULL) return 0;
10582         while(*s) i = i*259 + *s++;
10583         return i;
10584 }
10585
10586 int
10587 GameCheckSum ()
10588 {
10589         int i, sum=0;
10590         for(i=backwardMostMove; i<forwardMostMove; i++) {
10591                 sum += pvInfoList[i].depth;
10592                 sum += StringCheckSum(parseList[i]);
10593                 sum += StringCheckSum(commentList[i]);
10594                 sum *= 261;
10595         }
10596         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10597         return sum + StringCheckSum(commentList[i]);
10598 } // end of save patch
10599
10600 void
10601 GameEnds (ChessMove result, char *resultDetails, int whosays)
10602 {
10603     GameMode nextGameMode;
10604     int isIcsGame;
10605     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10606
10607     if(endingGame) return; /* [HGM] crash: forbid recursion */
10608     endingGame = 1;
10609     if(twoBoards) { // [HGM] dual: switch back to one board
10610         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10611         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10612     }
10613     if (appData.debugMode) {
10614       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10615               result, resultDetails ? resultDetails : "(null)", whosays);
10616     }
10617
10618     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10619
10620     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10621
10622     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10623         /* If we are playing on ICS, the server decides when the
10624            game is over, but the engine can offer to draw, claim
10625            a draw, or resign.
10626          */
10627 #if ZIPPY
10628         if (appData.zippyPlay && first.initDone) {
10629             if (result == GameIsDrawn) {
10630                 /* In case draw still needs to be claimed */
10631                 SendToICS(ics_prefix);
10632                 SendToICS("draw\n");
10633             } else if (StrCaseStr(resultDetails, "resign")) {
10634                 SendToICS(ics_prefix);
10635                 SendToICS("resign\n");
10636             }
10637         }
10638 #endif
10639         endingGame = 0; /* [HGM] crash */
10640         return;
10641     }
10642
10643     /* If we're loading the game from a file, stop */
10644     if (whosays == GE_FILE) {
10645       (void) StopLoadGameTimer();
10646       gameFileFP = NULL;
10647     }
10648
10649     /* Cancel draw offers */
10650     first.offeredDraw = second.offeredDraw = 0;
10651
10652     /* If this is an ICS game, only ICS can really say it's done;
10653        if not, anyone can. */
10654     isIcsGame = (gameMode == IcsPlayingWhite ||
10655                  gameMode == IcsPlayingBlack ||
10656                  gameMode == IcsObserving    ||
10657                  gameMode == IcsExamining);
10658
10659     if (!isIcsGame || whosays == GE_ICS) {
10660         /* OK -- not an ICS game, or ICS said it was done */
10661         StopClocks();
10662         if (!isIcsGame && !appData.noChessProgram)
10663           SetUserThinkingEnables();
10664
10665         /* [HGM] if a machine claims the game end we verify this claim */
10666         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10667             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10668                 char claimer;
10669                 ChessMove trueResult = (ChessMove) -1;
10670
10671                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10672                                             first.twoMachinesColor[0] :
10673                                             second.twoMachinesColor[0] ;
10674
10675                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10676                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10677                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10678                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10679                 } else
10680                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10681                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10682                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10683                 } else
10684                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10685                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10686                 }
10687
10688                 // now verify win claims, but not in drop games, as we don't understand those yet
10689                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10690                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10691                     (result == WhiteWins && claimer == 'w' ||
10692                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10693                       if (appData.debugMode) {
10694                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10695                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10696                       }
10697                       if(result != trueResult) {
10698                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10699                               result = claimer == 'w' ? BlackWins : WhiteWins;
10700                               resultDetails = buf;
10701                       }
10702                 } else
10703                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10704                     && (forwardMostMove <= backwardMostMove ||
10705                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10706                         (claimer=='b')==(forwardMostMove&1))
10707                                                                                   ) {
10708                       /* [HGM] verify: draws that were not flagged are false claims */
10709                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10710                       result = claimer == 'w' ? BlackWins : WhiteWins;
10711                       resultDetails = buf;
10712                 }
10713                 /* (Claiming a loss is accepted no questions asked!) */
10714             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10715                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10716                 result = GameUnfinished;
10717                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10718             }
10719             /* [HGM] bare: don't allow bare King to win */
10720             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10721                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10722                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10723                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10724                && result != GameIsDrawn)
10725             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10726                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10727                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10728                         if(p >= 0 && p <= (int)WhiteKing) k++;
10729                 }
10730                 if (appData.debugMode) {
10731                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10732                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10733                 }
10734                 if(k <= 1) {
10735                         result = GameIsDrawn;
10736                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10737                         resultDetails = buf;
10738                 }
10739             }
10740         }
10741
10742
10743         if(serverMoves != NULL && !loadFlag) { char c = '=';
10744             if(result==WhiteWins) c = '+';
10745             if(result==BlackWins) c = '-';
10746             if(resultDetails != NULL)
10747                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10748         }
10749         if (resultDetails != NULL) {
10750             gameInfo.result = result;
10751             gameInfo.resultDetails = StrSave(resultDetails);
10752
10753             /* display last move only if game was not loaded from file */
10754             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10755                 DisplayMove(currentMove - 1);
10756
10757             if (forwardMostMove != 0) {
10758                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10759                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10760                                                                 ) {
10761                     if (*appData.saveGameFile != NULLCHAR) {
10762                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10763                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10764                         else
10765                         SaveGameToFile(appData.saveGameFile, TRUE);
10766                     } else if (appData.autoSaveGames) {
10767                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10768                     }
10769                     if (*appData.savePositionFile != NULLCHAR) {
10770                         SavePositionToFile(appData.savePositionFile);
10771                     }
10772                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10773                 }
10774             }
10775
10776             /* Tell program how game ended in case it is learning */
10777             /* [HGM] Moved this to after saving the PGN, just in case */
10778             /* engine died and we got here through time loss. In that */
10779             /* case we will get a fatal error writing the pipe, which */
10780             /* would otherwise lose us the PGN.                       */
10781             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10782             /* output during GameEnds should never be fatal anymore   */
10783             if (gameMode == MachinePlaysWhite ||
10784                 gameMode == MachinePlaysBlack ||
10785                 gameMode == TwoMachinesPlay ||
10786                 gameMode == IcsPlayingWhite ||
10787                 gameMode == IcsPlayingBlack ||
10788                 gameMode == BeginningOfGame) {
10789                 char buf[MSG_SIZ];
10790                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10791                         resultDetails);
10792                 if (first.pr != NoProc) {
10793                     SendToProgram(buf, &first);
10794                 }
10795                 if (second.pr != NoProc &&
10796                     gameMode == TwoMachinesPlay) {
10797                     SendToProgram(buf, &second);
10798                 }
10799             }
10800         }
10801
10802         if (appData.icsActive) {
10803             if (appData.quietPlay &&
10804                 (gameMode == IcsPlayingWhite ||
10805                  gameMode == IcsPlayingBlack)) {
10806                 SendToICS(ics_prefix);
10807                 SendToICS("set shout 1\n");
10808             }
10809             nextGameMode = IcsIdle;
10810             ics_user_moved = FALSE;
10811             /* clean up premove.  It's ugly when the game has ended and the
10812              * premove highlights are still on the board.
10813              */
10814             if (gotPremove) {
10815               gotPremove = FALSE;
10816               ClearPremoveHighlights();
10817               DrawPosition(FALSE, boards[currentMove]);
10818             }
10819             if (whosays == GE_ICS) {
10820                 switch (result) {
10821                 case WhiteWins:
10822                     if (gameMode == IcsPlayingWhite)
10823                         PlayIcsWinSound();
10824                     else if(gameMode == IcsPlayingBlack)
10825                         PlayIcsLossSound();
10826                     break;
10827                 case BlackWins:
10828                     if (gameMode == IcsPlayingBlack)
10829                         PlayIcsWinSound();
10830                     else if(gameMode == IcsPlayingWhite)
10831                         PlayIcsLossSound();
10832                     break;
10833                 case GameIsDrawn:
10834                     PlayIcsDrawSound();
10835                     break;
10836                 default:
10837                     PlayIcsUnfinishedSound();
10838                 }
10839             }
10840         } else if (gameMode == EditGame ||
10841                    gameMode == PlayFromGameFile ||
10842                    gameMode == AnalyzeMode ||
10843                    gameMode == AnalyzeFile) {
10844             nextGameMode = gameMode;
10845         } else {
10846             nextGameMode = EndOfGame;
10847         }
10848         pausing = FALSE;
10849         ModeHighlight();
10850     } else {
10851         nextGameMode = gameMode;
10852     }
10853
10854     if (appData.noChessProgram) {
10855         gameMode = nextGameMode;
10856         ModeHighlight();
10857         endingGame = 0; /* [HGM] crash */
10858         return;
10859     }
10860
10861     if (first.reuse) {
10862         /* Put first chess program into idle state */
10863         if (first.pr != NoProc &&
10864             (gameMode == MachinePlaysWhite ||
10865              gameMode == MachinePlaysBlack ||
10866              gameMode == TwoMachinesPlay ||
10867              gameMode == IcsPlayingWhite ||
10868              gameMode == IcsPlayingBlack ||
10869              gameMode == BeginningOfGame)) {
10870             SendToProgram("force\n", &first);
10871             if (first.usePing) {
10872               char buf[MSG_SIZ];
10873               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10874               SendToProgram(buf, &first);
10875             }
10876         }
10877     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10878         /* Kill off first chess program */
10879         if (first.isr != NULL)
10880           RemoveInputSource(first.isr);
10881         first.isr = NULL;
10882
10883         if (first.pr != NoProc) {
10884             ExitAnalyzeMode();
10885             DoSleep( appData.delayBeforeQuit );
10886             SendToProgram("quit\n", &first);
10887             DoSleep( appData.delayAfterQuit );
10888             DestroyChildProcess(first.pr, first.useSigterm);
10889             first.reload = TRUE;
10890         }
10891         first.pr = NoProc;
10892     }
10893     if (second.reuse) {
10894         /* Put second chess program into idle state */
10895         if (second.pr != NoProc &&
10896             gameMode == TwoMachinesPlay) {
10897             SendToProgram("force\n", &second);
10898             if (second.usePing) {
10899               char buf[MSG_SIZ];
10900               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10901               SendToProgram(buf, &second);
10902             }
10903         }
10904     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10905         /* Kill off second chess program */
10906         if (second.isr != NULL)
10907           RemoveInputSource(second.isr);
10908         second.isr = NULL;
10909
10910         if (second.pr != NoProc) {
10911             DoSleep( appData.delayBeforeQuit );
10912             SendToProgram("quit\n", &second);
10913             DoSleep( appData.delayAfterQuit );
10914             DestroyChildProcess(second.pr, second.useSigterm);
10915             second.reload = TRUE;
10916         }
10917         second.pr = NoProc;
10918     }
10919
10920     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10921         char resChar = '=';
10922         switch (result) {
10923         case WhiteWins:
10924           resChar = '+';
10925           if (first.twoMachinesColor[0] == 'w') {
10926             first.matchWins++;
10927           } else {
10928             second.matchWins++;
10929           }
10930           break;
10931         case BlackWins:
10932           resChar = '-';
10933           if (first.twoMachinesColor[0] == 'b') {
10934             first.matchWins++;
10935           } else {
10936             second.matchWins++;
10937           }
10938           break;
10939         case GameUnfinished:
10940           resChar = ' ';
10941         default:
10942           break;
10943         }
10944
10945         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10946         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10947             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10948             ReserveGame(nextGame, resChar); // sets nextGame
10949             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10950             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10951         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10952
10953         if (nextGame <= appData.matchGames && !abortMatch) {
10954             gameMode = nextGameMode;
10955             matchGame = nextGame; // this will be overruled in tourney mode!
10956             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10957             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10958             endingGame = 0; /* [HGM] crash */
10959             return;
10960         } else {
10961             gameMode = nextGameMode;
10962             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10963                      first.tidy, second.tidy,
10964                      first.matchWins, second.matchWins,
10965                      appData.matchGames - (first.matchWins + second.matchWins));
10966             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10967             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10968             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10969             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10970                 first.twoMachinesColor = "black\n";
10971                 second.twoMachinesColor = "white\n";
10972             } else {
10973                 first.twoMachinesColor = "white\n";
10974                 second.twoMachinesColor = "black\n";
10975             }
10976         }
10977     }
10978     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10979         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10980       ExitAnalyzeMode();
10981     gameMode = nextGameMode;
10982     ModeHighlight();
10983     endingGame = 0;  /* [HGM] crash */
10984     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10985         if(matchMode == TRUE) { // match through command line: exit with or without popup
10986             if(ranking) {
10987                 ToNrEvent(forwardMostMove);
10988                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10989                 else ExitEvent(0);
10990             } else DisplayFatalError(buf, 0, 0);
10991         } else { // match through menu; just stop, with or without popup
10992             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10993             ModeHighlight();
10994             if(ranking){
10995                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10996             } else DisplayNote(buf);
10997       }
10998       if(ranking) free(ranking);
10999     }
11000 }
11001
11002 /* Assumes program was just initialized (initString sent).
11003    Leaves program in force mode. */
11004 void
11005 FeedMovesToProgram (ChessProgramState *cps, int upto)
11006 {
11007     int i;
11008
11009     if (appData.debugMode)
11010       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11011               startedFromSetupPosition ? "position and " : "",
11012               backwardMostMove, upto, cps->which);
11013     if(currentlyInitializedVariant != gameInfo.variant) {
11014       char buf[MSG_SIZ];
11015         // [HGM] variantswitch: make engine aware of new variant
11016         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11017                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11018         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11019         SendToProgram(buf, cps);
11020         currentlyInitializedVariant = gameInfo.variant;
11021     }
11022     SendToProgram("force\n", cps);
11023     if (startedFromSetupPosition) {
11024         SendBoard(cps, backwardMostMove);
11025     if (appData.debugMode) {
11026         fprintf(debugFP, "feedMoves\n");
11027     }
11028     }
11029     for (i = backwardMostMove; i < upto; i++) {
11030         SendMoveToProgram(i, cps);
11031     }
11032 }
11033
11034
11035 int
11036 ResurrectChessProgram ()
11037 {
11038      /* The chess program may have exited.
11039         If so, restart it and feed it all the moves made so far. */
11040     static int doInit = 0;
11041
11042     if (appData.noChessProgram) return 1;
11043
11044     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11045         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11046         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11047         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11048     } else {
11049         if (first.pr != NoProc) return 1;
11050         StartChessProgram(&first);
11051     }
11052     InitChessProgram(&first, FALSE);
11053     FeedMovesToProgram(&first, currentMove);
11054
11055     if (!first.sendTime) {
11056         /* can't tell gnuchess what its clock should read,
11057            so we bow to its notion. */
11058         ResetClocks();
11059         timeRemaining[0][currentMove] = whiteTimeRemaining;
11060         timeRemaining[1][currentMove] = blackTimeRemaining;
11061     }
11062
11063     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11064                 appData.icsEngineAnalyze) && first.analysisSupport) {
11065       SendToProgram("analyze\n", &first);
11066       first.analyzing = TRUE;
11067     }
11068     return 1;
11069 }
11070
11071 /*
11072  * Button procedures
11073  */
11074 void
11075 Reset (int redraw, int init)
11076 {
11077     int i;
11078
11079     if (appData.debugMode) {
11080         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11081                 redraw, init, gameMode);
11082     }
11083     CleanupTail(); // [HGM] vari: delete any stored variations
11084     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11085     pausing = pauseExamInvalid = FALSE;
11086     startedFromSetupPosition = blackPlaysFirst = FALSE;
11087     firstMove = TRUE;
11088     whiteFlag = blackFlag = FALSE;
11089     userOfferedDraw = FALSE;
11090     hintRequested = bookRequested = FALSE;
11091     first.maybeThinking = FALSE;
11092     second.maybeThinking = FALSE;
11093     first.bookSuspend = FALSE; // [HGM] book
11094     second.bookSuspend = FALSE;
11095     thinkOutput[0] = NULLCHAR;
11096     lastHint[0] = NULLCHAR;
11097     ClearGameInfo(&gameInfo);
11098     gameInfo.variant = StringToVariant(appData.variant);
11099     ics_user_moved = ics_clock_paused = FALSE;
11100     ics_getting_history = H_FALSE;
11101     ics_gamenum = -1;
11102     white_holding[0] = black_holding[0] = NULLCHAR;
11103     ClearProgramStats();
11104     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11105
11106     ResetFrontEnd();
11107     ClearHighlights();
11108     flipView = appData.flipView;
11109     ClearPremoveHighlights();
11110     gotPremove = FALSE;
11111     alarmSounded = FALSE;
11112
11113     GameEnds(EndOfFile, NULL, GE_PLAYER);
11114     if(appData.serverMovesName != NULL) {
11115         /* [HGM] prepare to make moves file for broadcasting */
11116         clock_t t = clock();
11117         if(serverMoves != NULL) fclose(serverMoves);
11118         serverMoves = fopen(appData.serverMovesName, "r");
11119         if(serverMoves != NULL) {
11120             fclose(serverMoves);
11121             /* delay 15 sec before overwriting, so all clients can see end */
11122             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11123         }
11124         serverMoves = fopen(appData.serverMovesName, "w");
11125     }
11126
11127     ExitAnalyzeMode();
11128     gameMode = BeginningOfGame;
11129     ModeHighlight();
11130     if(appData.icsActive) gameInfo.variant = VariantNormal;
11131     currentMove = forwardMostMove = backwardMostMove = 0;
11132     MarkTargetSquares(1);
11133     InitPosition(redraw);
11134     for (i = 0; i < MAX_MOVES; i++) {
11135         if (commentList[i] != NULL) {
11136             free(commentList[i]);
11137             commentList[i] = NULL;
11138         }
11139     }
11140     ResetClocks();
11141     timeRemaining[0][0] = whiteTimeRemaining;
11142     timeRemaining[1][0] = blackTimeRemaining;
11143
11144     if (first.pr == NoProc) {
11145         StartChessProgram(&first);
11146     }
11147     if (init) {
11148             InitChessProgram(&first, startedFromSetupPosition);
11149     }
11150     DisplayTitle("");
11151     DisplayMessage("", "");
11152     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11153     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11154     ClearMap();        // [HGM] exclude: invalidate map
11155 }
11156
11157 void
11158 AutoPlayGameLoop ()
11159 {
11160     for (;;) {
11161         if (!AutoPlayOneMove())
11162           return;
11163         if (matchMode || appData.timeDelay == 0)
11164           continue;
11165         if (appData.timeDelay < 0)
11166           return;
11167         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11168         break;
11169     }
11170 }
11171
11172 void
11173 AnalyzeNextGame()
11174 {
11175     ReloadGame(1); // next game
11176 }
11177
11178 int
11179 AutoPlayOneMove ()
11180 {
11181     int fromX, fromY, toX, toY;
11182
11183     if (appData.debugMode) {
11184       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11185     }
11186
11187     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11188       return FALSE;
11189
11190     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11191       pvInfoList[currentMove].depth = programStats.depth;
11192       pvInfoList[currentMove].score = programStats.score;
11193       pvInfoList[currentMove].time  = 0;
11194       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11195       else { // append analysis of final position as comment
11196         char buf[MSG_SIZ];
11197         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11198         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11199       }
11200       programStats.depth = 0;
11201     }
11202
11203     if (currentMove >= forwardMostMove) {
11204       if(gameMode == AnalyzeFile) {
11205           if(appData.loadGameIndex == -1) {
11206             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11207           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11208           } else {
11209           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11210         }
11211       }
11212 //      gameMode = EndOfGame;
11213 //      ModeHighlight();
11214
11215       /* [AS] Clear current move marker at the end of a game */
11216       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11217
11218       return FALSE;
11219     }
11220
11221     toX = moveList[currentMove][2] - AAA;
11222     toY = moveList[currentMove][3] - ONE;
11223
11224     if (moveList[currentMove][1] == '@') {
11225         if (appData.highlightLastMove) {
11226             SetHighlights(-1, -1, toX, toY);
11227         }
11228     } else {
11229         fromX = moveList[currentMove][0] - AAA;
11230         fromY = moveList[currentMove][1] - ONE;
11231
11232         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11233
11234         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11235
11236         if (appData.highlightLastMove) {
11237             SetHighlights(fromX, fromY, toX, toY);
11238         }
11239     }
11240     DisplayMove(currentMove);
11241     SendMoveToProgram(currentMove++, &first);
11242     DisplayBothClocks();
11243     DrawPosition(FALSE, boards[currentMove]);
11244     // [HGM] PV info: always display, routine tests if empty
11245     DisplayComment(currentMove - 1, commentList[currentMove]);
11246     return TRUE;
11247 }
11248
11249
11250 int
11251 LoadGameOneMove (ChessMove readAhead)
11252 {
11253     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11254     char promoChar = NULLCHAR;
11255     ChessMove moveType;
11256     char move[MSG_SIZ];
11257     char *p, *q;
11258
11259     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11260         gameMode != AnalyzeMode && gameMode != Training) {
11261         gameFileFP = NULL;
11262         return FALSE;
11263     }
11264
11265     yyboardindex = forwardMostMove;
11266     if (readAhead != EndOfFile) {
11267       moveType = readAhead;
11268     } else {
11269       if (gameFileFP == NULL)
11270           return FALSE;
11271       moveType = (ChessMove) Myylex();
11272     }
11273
11274     done = FALSE;
11275     switch (moveType) {
11276       case Comment:
11277         if (appData.debugMode)
11278           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11279         p = yy_text;
11280
11281         /* append the comment but don't display it */
11282         AppendComment(currentMove, p, FALSE);
11283         return TRUE;
11284
11285       case WhiteCapturesEnPassant:
11286       case BlackCapturesEnPassant:
11287       case WhitePromotion:
11288       case BlackPromotion:
11289       case WhiteNonPromotion:
11290       case BlackNonPromotion:
11291       case NormalMove:
11292       case WhiteKingSideCastle:
11293       case WhiteQueenSideCastle:
11294       case BlackKingSideCastle:
11295       case BlackQueenSideCastle:
11296       case WhiteKingSideCastleWild:
11297       case WhiteQueenSideCastleWild:
11298       case BlackKingSideCastleWild:
11299       case BlackQueenSideCastleWild:
11300       /* PUSH Fabien */
11301       case WhiteHSideCastleFR:
11302       case WhiteASideCastleFR:
11303       case BlackHSideCastleFR:
11304       case BlackASideCastleFR:
11305       /* POP Fabien */
11306         if (appData.debugMode)
11307           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11308         fromX = currentMoveString[0] - AAA;
11309         fromY = currentMoveString[1] - ONE;
11310         toX = currentMoveString[2] - AAA;
11311         toY = currentMoveString[3] - ONE;
11312         promoChar = currentMoveString[4];
11313         break;
11314
11315       case WhiteDrop:
11316       case BlackDrop:
11317         if (appData.debugMode)
11318           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11319         fromX = moveType == WhiteDrop ?
11320           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11321         (int) CharToPiece(ToLower(currentMoveString[0]));
11322         fromY = DROP_RANK;
11323         toX = currentMoveString[2] - AAA;
11324         toY = currentMoveString[3] - ONE;
11325         break;
11326
11327       case WhiteWins:
11328       case BlackWins:
11329       case GameIsDrawn:
11330       case GameUnfinished:
11331         if (appData.debugMode)
11332           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11333         p = strchr(yy_text, '{');
11334         if (p == NULL) p = strchr(yy_text, '(');
11335         if (p == NULL) {
11336             p = yy_text;
11337             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11338         } else {
11339             q = strchr(p, *p == '{' ? '}' : ')');
11340             if (q != NULL) *q = NULLCHAR;
11341             p++;
11342         }
11343         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11344         GameEnds(moveType, p, GE_FILE);
11345         done = TRUE;
11346         if (cmailMsgLoaded) {
11347             ClearHighlights();
11348             flipView = WhiteOnMove(currentMove);
11349             if (moveType == GameUnfinished) flipView = !flipView;
11350             if (appData.debugMode)
11351               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11352         }
11353         break;
11354
11355       case EndOfFile:
11356         if (appData.debugMode)
11357           fprintf(debugFP, "Parser hit end of file\n");
11358         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11359           case MT_NONE:
11360           case MT_CHECK:
11361             break;
11362           case MT_CHECKMATE:
11363           case MT_STAINMATE:
11364             if (WhiteOnMove(currentMove)) {
11365                 GameEnds(BlackWins, "Black mates", GE_FILE);
11366             } else {
11367                 GameEnds(WhiteWins, "White mates", GE_FILE);
11368             }
11369             break;
11370           case MT_STALEMATE:
11371             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11372             break;
11373         }
11374         done = TRUE;
11375         break;
11376
11377       case MoveNumberOne:
11378         if (lastLoadGameStart == GNUChessGame) {
11379             /* GNUChessGames have numbers, but they aren't move numbers */
11380             if (appData.debugMode)
11381               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11382                       yy_text, (int) moveType);
11383             return LoadGameOneMove(EndOfFile); /* tail recursion */
11384         }
11385         /* else fall thru */
11386
11387       case XBoardGame:
11388       case GNUChessGame:
11389       case PGNTag:
11390         /* Reached start of next game in file */
11391         if (appData.debugMode)
11392           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11393         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11394           case MT_NONE:
11395           case MT_CHECK:
11396             break;
11397           case MT_CHECKMATE:
11398           case MT_STAINMATE:
11399             if (WhiteOnMove(currentMove)) {
11400                 GameEnds(BlackWins, "Black mates", GE_FILE);
11401             } else {
11402                 GameEnds(WhiteWins, "White mates", GE_FILE);
11403             }
11404             break;
11405           case MT_STALEMATE:
11406             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11407             break;
11408         }
11409         done = TRUE;
11410         break;
11411
11412       case PositionDiagram:     /* should not happen; ignore */
11413       case ElapsedTime:         /* ignore */
11414       case NAG:                 /* ignore */
11415         if (appData.debugMode)
11416           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11417                   yy_text, (int) moveType);
11418         return LoadGameOneMove(EndOfFile); /* tail recursion */
11419
11420       case IllegalMove:
11421         if (appData.testLegality) {
11422             if (appData.debugMode)
11423               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11424             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11425                     (forwardMostMove / 2) + 1,
11426                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11427             DisplayError(move, 0);
11428             done = TRUE;
11429         } else {
11430             if (appData.debugMode)
11431               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11432                       yy_text, currentMoveString);
11433             fromX = currentMoveString[0] - AAA;
11434             fromY = currentMoveString[1] - ONE;
11435             toX = currentMoveString[2] - AAA;
11436             toY = currentMoveString[3] - ONE;
11437             promoChar = currentMoveString[4];
11438         }
11439         break;
11440
11441       case AmbiguousMove:
11442         if (appData.debugMode)
11443           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11444         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11445                 (forwardMostMove / 2) + 1,
11446                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11447         DisplayError(move, 0);
11448         done = TRUE;
11449         break;
11450
11451       default:
11452       case ImpossibleMove:
11453         if (appData.debugMode)
11454           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11455         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11456                 (forwardMostMove / 2) + 1,
11457                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11458         DisplayError(move, 0);
11459         done = TRUE;
11460         break;
11461     }
11462
11463     if (done) {
11464         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11465             DrawPosition(FALSE, boards[currentMove]);
11466             DisplayBothClocks();
11467             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11468               DisplayComment(currentMove - 1, commentList[currentMove]);
11469         }
11470         (void) StopLoadGameTimer();
11471         gameFileFP = NULL;
11472         cmailOldMove = forwardMostMove;
11473         return FALSE;
11474     } else {
11475         /* currentMoveString is set as a side-effect of yylex */
11476
11477         thinkOutput[0] = NULLCHAR;
11478         MakeMove(fromX, fromY, toX, toY, promoChar);
11479         currentMove = forwardMostMove;
11480         return TRUE;
11481     }
11482 }
11483
11484 /* Load the nth game from the given file */
11485 int
11486 LoadGameFromFile (char *filename, int n, char *title, int useList)
11487 {
11488     FILE *f;
11489     char buf[MSG_SIZ];
11490
11491     if (strcmp(filename, "-") == 0) {
11492         f = stdin;
11493         title = "stdin";
11494     } else {
11495         f = fopen(filename, "rb");
11496         if (f == NULL) {
11497           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11498             DisplayError(buf, errno);
11499             return FALSE;
11500         }
11501     }
11502     if (fseek(f, 0, 0) == -1) {
11503         /* f is not seekable; probably a pipe */
11504         useList = FALSE;
11505     }
11506     if (useList && n == 0) {
11507         int error = GameListBuild(f);
11508         if (error) {
11509             DisplayError(_("Cannot build game list"), error);
11510         } else if (!ListEmpty(&gameList) &&
11511                    ((ListGame *) gameList.tailPred)->number > 1) {
11512             GameListPopUp(f, title);
11513             return TRUE;
11514         }
11515         GameListDestroy();
11516         n = 1;
11517     }
11518     if (n == 0) n = 1;
11519     return LoadGame(f, n, title, FALSE);
11520 }
11521
11522
11523 void
11524 MakeRegisteredMove ()
11525 {
11526     int fromX, fromY, toX, toY;
11527     char promoChar;
11528     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11529         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11530           case CMAIL_MOVE:
11531           case CMAIL_DRAW:
11532             if (appData.debugMode)
11533               fprintf(debugFP, "Restoring %s for game %d\n",
11534                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11535
11536             thinkOutput[0] = NULLCHAR;
11537             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11538             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11539             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11540             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11541             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11542             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11543             MakeMove(fromX, fromY, toX, toY, promoChar);
11544             ShowMove(fromX, fromY, toX, toY);
11545
11546             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11547               case MT_NONE:
11548               case MT_CHECK:
11549                 break;
11550
11551               case MT_CHECKMATE:
11552               case MT_STAINMATE:
11553                 if (WhiteOnMove(currentMove)) {
11554                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11555                 } else {
11556                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11557                 }
11558                 break;
11559
11560               case MT_STALEMATE:
11561                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11562                 break;
11563             }
11564
11565             break;
11566
11567           case CMAIL_RESIGN:
11568             if (WhiteOnMove(currentMove)) {
11569                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11570             } else {
11571                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11572             }
11573             break;
11574
11575           case CMAIL_ACCEPT:
11576             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11577             break;
11578
11579           default:
11580             break;
11581         }
11582     }
11583
11584     return;
11585 }
11586
11587 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11588 int
11589 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11590 {
11591     int retVal;
11592
11593     if (gameNumber > nCmailGames) {
11594         DisplayError(_("No more games in this message"), 0);
11595         return FALSE;
11596     }
11597     if (f == lastLoadGameFP) {
11598         int offset = gameNumber - lastLoadGameNumber;
11599         if (offset == 0) {
11600             cmailMsg[0] = NULLCHAR;
11601             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11602                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11603                 nCmailMovesRegistered--;
11604             }
11605             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11606             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11607                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11608             }
11609         } else {
11610             if (! RegisterMove()) return FALSE;
11611         }
11612     }
11613
11614     retVal = LoadGame(f, gameNumber, title, useList);
11615
11616     /* Make move registered during previous look at this game, if any */
11617     MakeRegisteredMove();
11618
11619     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11620         commentList[currentMove]
11621           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11622         DisplayComment(currentMove - 1, commentList[currentMove]);
11623     }
11624
11625     return retVal;
11626 }
11627
11628 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11629 int
11630 ReloadGame (int offset)
11631 {
11632     int gameNumber = lastLoadGameNumber + offset;
11633     if (lastLoadGameFP == NULL) {
11634         DisplayError(_("No game has been loaded yet"), 0);
11635         return FALSE;
11636     }
11637     if (gameNumber <= 0) {
11638         DisplayError(_("Can't back up any further"), 0);
11639         return FALSE;
11640     }
11641     if (cmailMsgLoaded) {
11642         return CmailLoadGame(lastLoadGameFP, gameNumber,
11643                              lastLoadGameTitle, lastLoadGameUseList);
11644     } else {
11645         return LoadGame(lastLoadGameFP, gameNumber,
11646                         lastLoadGameTitle, lastLoadGameUseList);
11647     }
11648 }
11649
11650 int keys[EmptySquare+1];
11651
11652 int
11653 PositionMatches (Board b1, Board b2)
11654 {
11655     int r, f, sum=0;
11656     switch(appData.searchMode) {
11657         case 1: return CompareWithRights(b1, b2);
11658         case 2:
11659             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11660                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11661             }
11662             return TRUE;
11663         case 3:
11664             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11665               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11666                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11667             }
11668             return sum==0;
11669         case 4:
11670             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11671                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11672             }
11673             return sum==0;
11674     }
11675     return TRUE;
11676 }
11677
11678 #define Q_PROMO  4
11679 #define Q_EP     3
11680 #define Q_BCASTL 2
11681 #define Q_WCASTL 1
11682
11683 int pieceList[256], quickBoard[256];
11684 ChessSquare pieceType[256] = { EmptySquare };
11685 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11686 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11687 int soughtTotal, turn;
11688 Boolean epOK, flipSearch;
11689
11690 typedef struct {
11691     unsigned char piece, to;
11692 } Move;
11693
11694 #define DSIZE (250000)
11695
11696 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11697 Move *moveDatabase = initialSpace;
11698 unsigned int movePtr, dataSize = DSIZE;
11699
11700 int
11701 MakePieceList (Board board, int *counts)
11702 {
11703     int r, f, n=Q_PROMO, total=0;
11704     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11705     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11706         int sq = f + (r<<4);
11707         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11708             quickBoard[sq] = ++n;
11709             pieceList[n] = sq;
11710             pieceType[n] = board[r][f];
11711             counts[board[r][f]]++;
11712             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11713             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11714             total++;
11715         }
11716     }
11717     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11718     return total;
11719 }
11720
11721 void
11722 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11723 {
11724     int sq = fromX + (fromY<<4);
11725     int piece = quickBoard[sq], rook;
11726     quickBoard[sq] = 0;
11727     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11728     if(piece == pieceList[1] && fromY == toY) {
11729       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11730         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11731         moveDatabase[movePtr++].piece = Q_WCASTL;
11732         quickBoard[sq] = piece;
11733         piece = quickBoard[from]; quickBoard[from] = 0;
11734         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11735       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
11736         quickBoard[sq] = 0; // remove Rook
11737         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
11738         moveDatabase[movePtr++].piece = Q_WCASTL;
11739         quickBoard[sq] = pieceList[1]; // put King
11740         piece = rook;
11741         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
11742       }
11743     } else
11744     if(piece == pieceList[2] && fromY == toY) {
11745       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11746         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11747         moveDatabase[movePtr++].piece = Q_BCASTL;
11748         quickBoard[sq] = piece;
11749         piece = quickBoard[from]; quickBoard[from] = 0;
11750         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11751       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
11752         quickBoard[sq] = 0; // remove Rook
11753         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
11754         moveDatabase[movePtr++].piece = Q_BCASTL;
11755         quickBoard[sq] = pieceList[2]; // put King
11756         piece = rook;
11757         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
11758       }
11759     } else
11760     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11761         quickBoard[(fromY<<4)+toX] = 0;
11762         moveDatabase[movePtr].piece = Q_EP;
11763         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11764         moveDatabase[movePtr].to = sq;
11765     } else
11766     if(promoPiece != pieceType[piece]) {
11767         moveDatabase[movePtr++].piece = Q_PROMO;
11768         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11769     }
11770     moveDatabase[movePtr].piece = piece;
11771     quickBoard[sq] = piece;
11772     movePtr++;
11773 }
11774
11775 int
11776 PackGame (Board board)
11777 {
11778     Move *newSpace = NULL;
11779     moveDatabase[movePtr].piece = 0; // terminate previous game
11780     if(movePtr > dataSize) {
11781         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11782         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11783         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11784         if(newSpace) {
11785             int i;
11786             Move *p = moveDatabase, *q = newSpace;
11787             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11788             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11789             moveDatabase = newSpace;
11790         } else { // calloc failed, we must be out of memory. Too bad...
11791             dataSize = 0; // prevent calloc events for all subsequent games
11792             return 0;     // and signal this one isn't cached
11793         }
11794     }
11795     movePtr++;
11796     MakePieceList(board, counts);
11797     return movePtr;
11798 }
11799
11800 int
11801 QuickCompare (Board board, int *minCounts, int *maxCounts)
11802 {   // compare according to search mode
11803     int r, f;
11804     switch(appData.searchMode)
11805     {
11806       case 1: // exact position match
11807         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11808         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11809             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11810         }
11811         break;
11812       case 2: // can have extra material on empty squares
11813         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11814             if(board[r][f] == EmptySquare) continue;
11815             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11816         }
11817         break;
11818       case 3: // material with exact Pawn structure
11819         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11820             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11821             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11822         } // fall through to material comparison
11823       case 4: // exact material
11824         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11825         break;
11826       case 6: // material range with given imbalance
11827         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11828         // fall through to range comparison
11829       case 5: // material range
11830         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11831     }
11832     return TRUE;
11833 }
11834
11835 int
11836 QuickScan (Board board, Move *move)
11837 {   // reconstruct game,and compare all positions in it
11838     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11839     do {
11840         int piece = move->piece;
11841         int to = move->to, from = pieceList[piece];
11842         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11843           if(!piece) return -1;
11844           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11845             piece = (++move)->piece;
11846             from = pieceList[piece];
11847             counts[pieceType[piece]]--;
11848             pieceType[piece] = (ChessSquare) move->to;
11849             counts[move->to]++;
11850           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11851             counts[pieceType[quickBoard[to]]]--;
11852             quickBoard[to] = 0; total--;
11853             move++;
11854             continue;
11855           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11856             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11857             from  = pieceList[piece]; // so this must be King
11858             quickBoard[from] = 0;
11859             pieceList[piece] = to;
11860             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11861             quickBoard[from] = 0; // rook
11862             quickBoard[to] = piece;
11863             to = move->to; piece = move->piece;
11864             goto aftercastle;
11865           }
11866         }
11867         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11868         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11869         quickBoard[from] = 0;
11870       aftercastle:
11871         quickBoard[to] = piece;
11872         pieceList[piece] = to;
11873         cnt++; turn ^= 3;
11874         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11875            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11876            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11877                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11878           ) {
11879             static int lastCounts[EmptySquare+1];
11880             int i;
11881             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11882             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11883         } else stretch = 0;
11884         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11885         move++;
11886     } while(1);
11887 }
11888
11889 void
11890 InitSearch ()
11891 {
11892     int r, f;
11893     flipSearch = FALSE;
11894     CopyBoard(soughtBoard, boards[currentMove]);
11895     soughtTotal = MakePieceList(soughtBoard, maxSought);
11896     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11897     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11898     CopyBoard(reverseBoard, boards[currentMove]);
11899     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11900         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11901         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11902         reverseBoard[r][f] = piece;
11903     }
11904     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11905     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11906     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11907                  || (boards[currentMove][CASTLING][2] == NoRights ||
11908                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11909                  && (boards[currentMove][CASTLING][5] == NoRights ||
11910                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11911       ) {
11912         flipSearch = TRUE;
11913         CopyBoard(flipBoard, soughtBoard);
11914         CopyBoard(rotateBoard, reverseBoard);
11915         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11916             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11917             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11918         }
11919     }
11920     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11921     if(appData.searchMode >= 5) {
11922         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11923         MakePieceList(soughtBoard, minSought);
11924         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11925     }
11926     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11927         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11928 }
11929
11930 GameInfo dummyInfo;
11931 static int creatingBook;
11932
11933 int
11934 GameContainsPosition (FILE *f, ListGame *lg)
11935 {
11936     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11937     int fromX, fromY, toX, toY;
11938     char promoChar;
11939     static int initDone=FALSE;
11940
11941     // weed out games based on numerical tag comparison
11942     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11943     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11944     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11945     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11946     if(!initDone) {
11947         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11948         initDone = TRUE;
11949     }
11950     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11951     else CopyBoard(boards[scratch], initialPosition); // default start position
11952     if(lg->moves) {
11953         turn = btm + 1;
11954         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11955         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11956     }
11957     if(btm) plyNr++;
11958     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11959     fseek(f, lg->offset, 0);
11960     yynewfile(f);
11961     while(1) {
11962         yyboardindex = scratch;
11963         quickFlag = plyNr+1;
11964         next = Myylex();
11965         quickFlag = 0;
11966         switch(next) {
11967             case PGNTag:
11968                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11969             default:
11970                 continue;
11971
11972             case XBoardGame:
11973             case GNUChessGame:
11974                 if(plyNr) return -1; // after we have seen moves, this is for new game
11975               continue;
11976
11977             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11978             case ImpossibleMove:
11979             case WhiteWins: // game ends here with these four
11980             case BlackWins:
11981             case GameIsDrawn:
11982             case GameUnfinished:
11983                 return -1;
11984
11985             case IllegalMove:
11986                 if(appData.testLegality) return -1;
11987             case WhiteCapturesEnPassant:
11988             case BlackCapturesEnPassant:
11989             case WhitePromotion:
11990             case BlackPromotion:
11991             case WhiteNonPromotion:
11992             case BlackNonPromotion:
11993             case NormalMove:
11994             case WhiteKingSideCastle:
11995             case WhiteQueenSideCastle:
11996             case BlackKingSideCastle:
11997             case BlackQueenSideCastle:
11998             case WhiteKingSideCastleWild:
11999             case WhiteQueenSideCastleWild:
12000             case BlackKingSideCastleWild:
12001             case BlackQueenSideCastleWild:
12002             case WhiteHSideCastleFR:
12003             case WhiteASideCastleFR:
12004             case BlackHSideCastleFR:
12005             case BlackASideCastleFR:
12006                 fromX = currentMoveString[0] - AAA;
12007                 fromY = currentMoveString[1] - ONE;
12008                 toX = currentMoveString[2] - AAA;
12009                 toY = currentMoveString[3] - ONE;
12010                 promoChar = currentMoveString[4];
12011                 break;
12012             case WhiteDrop:
12013             case BlackDrop:
12014                 fromX = next == WhiteDrop ?
12015                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12016                   (int) CharToPiece(ToLower(currentMoveString[0]));
12017                 fromY = DROP_RANK;
12018                 toX = currentMoveString[2] - AAA;
12019                 toY = currentMoveString[3] - ONE;
12020                 promoChar = 0;
12021                 break;
12022         }
12023         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12024         plyNr++;
12025         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12026         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12027         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12028         if(appData.findMirror) {
12029             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12030             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12031         }
12032     }
12033 }
12034
12035 /* Load the nth game from open file f */
12036 int
12037 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12038 {
12039     ChessMove cm;
12040     char buf[MSG_SIZ];
12041     int gn = gameNumber;
12042     ListGame *lg = NULL;
12043     int numPGNTags = 0;
12044     int err, pos = -1;
12045     GameMode oldGameMode;
12046     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12047
12048     if (appData.debugMode)
12049         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12050
12051     if (gameMode == Training )
12052         SetTrainingModeOff();
12053
12054     oldGameMode = gameMode;
12055     if (gameMode != BeginningOfGame) {
12056       Reset(FALSE, TRUE);
12057     }
12058
12059     gameFileFP = f;
12060     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12061         fclose(lastLoadGameFP);
12062     }
12063
12064     if (useList) {
12065         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12066
12067         if (lg) {
12068             fseek(f, lg->offset, 0);
12069             GameListHighlight(gameNumber);
12070             pos = lg->position;
12071             gn = 1;
12072         }
12073         else {
12074             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12075               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12076             else
12077             DisplayError(_("Game number out of range"), 0);
12078             return FALSE;
12079         }
12080     } else {
12081         GameListDestroy();
12082         if (fseek(f, 0, 0) == -1) {
12083             if (f == lastLoadGameFP ?
12084                 gameNumber == lastLoadGameNumber + 1 :
12085                 gameNumber == 1) {
12086                 gn = 1;
12087             } else {
12088                 DisplayError(_("Can't seek on game file"), 0);
12089                 return FALSE;
12090             }
12091         }
12092     }
12093     lastLoadGameFP = f;
12094     lastLoadGameNumber = gameNumber;
12095     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12096     lastLoadGameUseList = useList;
12097
12098     yynewfile(f);
12099
12100     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12101       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12102                 lg->gameInfo.black);
12103             DisplayTitle(buf);
12104     } else if (*title != NULLCHAR) {
12105         if (gameNumber > 1) {
12106           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12107             DisplayTitle(buf);
12108         } else {
12109             DisplayTitle(title);
12110         }
12111     }
12112
12113     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12114         gameMode = PlayFromGameFile;
12115         ModeHighlight();
12116     }
12117
12118     currentMove = forwardMostMove = backwardMostMove = 0;
12119     CopyBoard(boards[0], initialPosition);
12120     StopClocks();
12121
12122     /*
12123      * Skip the first gn-1 games in the file.
12124      * Also skip over anything that precedes an identifiable
12125      * start of game marker, to avoid being confused by
12126      * garbage at the start of the file.  Currently
12127      * recognized start of game markers are the move number "1",
12128      * the pattern "gnuchess .* game", the pattern
12129      * "^[#;%] [^ ]* game file", and a PGN tag block.
12130      * A game that starts with one of the latter two patterns
12131      * will also have a move number 1, possibly
12132      * following a position diagram.
12133      * 5-4-02: Let's try being more lenient and allowing a game to
12134      * start with an unnumbered move.  Does that break anything?
12135      */
12136     cm = lastLoadGameStart = EndOfFile;
12137     while (gn > 0) {
12138         yyboardindex = forwardMostMove;
12139         cm = (ChessMove) Myylex();
12140         switch (cm) {
12141           case EndOfFile:
12142             if (cmailMsgLoaded) {
12143                 nCmailGames = CMAIL_MAX_GAMES - gn;
12144             } else {
12145                 Reset(TRUE, TRUE);
12146                 DisplayError(_("Game not found in file"), 0);
12147             }
12148             return FALSE;
12149
12150           case GNUChessGame:
12151           case XBoardGame:
12152             gn--;
12153             lastLoadGameStart = cm;
12154             break;
12155
12156           case MoveNumberOne:
12157             switch (lastLoadGameStart) {
12158               case GNUChessGame:
12159               case XBoardGame:
12160               case PGNTag:
12161                 break;
12162               case MoveNumberOne:
12163               case EndOfFile:
12164                 gn--;           /* count this game */
12165                 lastLoadGameStart = cm;
12166                 break;
12167               default:
12168                 /* impossible */
12169                 break;
12170             }
12171             break;
12172
12173           case PGNTag:
12174             switch (lastLoadGameStart) {
12175               case GNUChessGame:
12176               case PGNTag:
12177               case MoveNumberOne:
12178               case EndOfFile:
12179                 gn--;           /* count this game */
12180                 lastLoadGameStart = cm;
12181                 break;
12182               case XBoardGame:
12183                 lastLoadGameStart = cm; /* game counted already */
12184                 break;
12185               default:
12186                 /* impossible */
12187                 break;
12188             }
12189             if (gn > 0) {
12190                 do {
12191                     yyboardindex = forwardMostMove;
12192                     cm = (ChessMove) Myylex();
12193                 } while (cm == PGNTag || cm == Comment);
12194             }
12195             break;
12196
12197           case WhiteWins:
12198           case BlackWins:
12199           case GameIsDrawn:
12200             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12201                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12202                     != CMAIL_OLD_RESULT) {
12203                     nCmailResults ++ ;
12204                     cmailResult[  CMAIL_MAX_GAMES
12205                                 - gn - 1] = CMAIL_OLD_RESULT;
12206                 }
12207             }
12208             break;
12209
12210           case NormalMove:
12211             /* Only a NormalMove can be at the start of a game
12212              * without a position diagram. */
12213             if (lastLoadGameStart == EndOfFile ) {
12214               gn--;
12215               lastLoadGameStart = MoveNumberOne;
12216             }
12217             break;
12218
12219           default:
12220             break;
12221         }
12222     }
12223
12224     if (appData.debugMode)
12225       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12226
12227     if (cm == XBoardGame) {
12228         /* Skip any header junk before position diagram and/or move 1 */
12229         for (;;) {
12230             yyboardindex = forwardMostMove;
12231             cm = (ChessMove) Myylex();
12232
12233             if (cm == EndOfFile ||
12234                 cm == GNUChessGame || cm == XBoardGame) {
12235                 /* Empty game; pretend end-of-file and handle later */
12236                 cm = EndOfFile;
12237                 break;
12238             }
12239
12240             if (cm == MoveNumberOne || cm == PositionDiagram ||
12241                 cm == PGNTag || cm == Comment)
12242               break;
12243         }
12244     } else if (cm == GNUChessGame) {
12245         if (gameInfo.event != NULL) {
12246             free(gameInfo.event);
12247         }
12248         gameInfo.event = StrSave(yy_text);
12249     }
12250
12251     startedFromSetupPosition = FALSE;
12252     while (cm == PGNTag) {
12253         if (appData.debugMode)
12254           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12255         err = ParsePGNTag(yy_text, &gameInfo);
12256         if (!err) numPGNTags++;
12257
12258         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12259         if(gameInfo.variant != oldVariant) {
12260             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12261             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12262             InitPosition(TRUE);
12263             oldVariant = gameInfo.variant;
12264             if (appData.debugMode)
12265               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12266         }
12267
12268
12269         if (gameInfo.fen != NULL) {
12270           Board initial_position;
12271           startedFromSetupPosition = TRUE;
12272           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12273             Reset(TRUE, TRUE);
12274             DisplayError(_("Bad FEN position in file"), 0);
12275             return FALSE;
12276           }
12277           CopyBoard(boards[0], initial_position);
12278           if (blackPlaysFirst) {
12279             currentMove = forwardMostMove = backwardMostMove = 1;
12280             CopyBoard(boards[1], initial_position);
12281             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12282             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12283             timeRemaining[0][1] = whiteTimeRemaining;
12284             timeRemaining[1][1] = blackTimeRemaining;
12285             if (commentList[0] != NULL) {
12286               commentList[1] = commentList[0];
12287               commentList[0] = NULL;
12288             }
12289           } else {
12290             currentMove = forwardMostMove = backwardMostMove = 0;
12291           }
12292           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12293           {   int i;
12294               initialRulePlies = FENrulePlies;
12295               for( i=0; i< nrCastlingRights; i++ )
12296                   initialRights[i] = initial_position[CASTLING][i];
12297           }
12298           yyboardindex = forwardMostMove;
12299           free(gameInfo.fen);
12300           gameInfo.fen = NULL;
12301         }
12302
12303         yyboardindex = forwardMostMove;
12304         cm = (ChessMove) Myylex();
12305
12306         /* Handle comments interspersed among the tags */
12307         while (cm == Comment) {
12308             char *p;
12309             if (appData.debugMode)
12310               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12311             p = yy_text;
12312             AppendComment(currentMove, p, FALSE);
12313             yyboardindex = forwardMostMove;
12314             cm = (ChessMove) Myylex();
12315         }
12316     }
12317
12318     /* don't rely on existence of Event tag since if game was
12319      * pasted from clipboard the Event tag may not exist
12320      */
12321     if (numPGNTags > 0){
12322         char *tags;
12323         if (gameInfo.variant == VariantNormal) {
12324           VariantClass v = StringToVariant(gameInfo.event);
12325           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12326           if(v < VariantShogi) gameInfo.variant = v;
12327         }
12328         if (!matchMode) {
12329           if( appData.autoDisplayTags ) {
12330             tags = PGNTags(&gameInfo);
12331             TagsPopUp(tags, CmailMsg());
12332             free(tags);
12333           }
12334         }
12335     } else {
12336         /* Make something up, but don't display it now */
12337         SetGameInfo();
12338         TagsPopDown();
12339     }
12340
12341     if (cm == PositionDiagram) {
12342         int i, j;
12343         char *p;
12344         Board initial_position;
12345
12346         if (appData.debugMode)
12347           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12348
12349         if (!startedFromSetupPosition) {
12350             p = yy_text;
12351             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12352               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12353                 switch (*p) {
12354                   case '{':
12355                   case '[':
12356                   case '-':
12357                   case ' ':
12358                   case '\t':
12359                   case '\n':
12360                   case '\r':
12361                     break;
12362                   default:
12363                     initial_position[i][j++] = CharToPiece(*p);
12364                     break;
12365                 }
12366             while (*p == ' ' || *p == '\t' ||
12367                    *p == '\n' || *p == '\r') p++;
12368
12369             if (strncmp(p, "black", strlen("black"))==0)
12370               blackPlaysFirst = TRUE;
12371             else
12372               blackPlaysFirst = FALSE;
12373             startedFromSetupPosition = TRUE;
12374
12375             CopyBoard(boards[0], initial_position);
12376             if (blackPlaysFirst) {
12377                 currentMove = forwardMostMove = backwardMostMove = 1;
12378                 CopyBoard(boards[1], initial_position);
12379                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12380                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12381                 timeRemaining[0][1] = whiteTimeRemaining;
12382                 timeRemaining[1][1] = blackTimeRemaining;
12383                 if (commentList[0] != NULL) {
12384                     commentList[1] = commentList[0];
12385                     commentList[0] = NULL;
12386                 }
12387             } else {
12388                 currentMove = forwardMostMove = backwardMostMove = 0;
12389             }
12390         }
12391         yyboardindex = forwardMostMove;
12392         cm = (ChessMove) Myylex();
12393     }
12394
12395   if(!creatingBook) {
12396     if (first.pr == NoProc) {
12397         StartChessProgram(&first);
12398     }
12399     InitChessProgram(&first, FALSE);
12400     SendToProgram("force\n", &first);
12401     if (startedFromSetupPosition) {
12402         SendBoard(&first, forwardMostMove);
12403     if (appData.debugMode) {
12404         fprintf(debugFP, "Load Game\n");
12405     }
12406         DisplayBothClocks();
12407     }
12408   }
12409
12410     /* [HGM] server: flag to write setup moves in broadcast file as one */
12411     loadFlag = appData.suppressLoadMoves;
12412
12413     while (cm == Comment) {
12414         char *p;
12415         if (appData.debugMode)
12416           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12417         p = yy_text;
12418         AppendComment(currentMove, p, FALSE);
12419         yyboardindex = forwardMostMove;
12420         cm = (ChessMove) Myylex();
12421     }
12422
12423     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12424         cm == WhiteWins || cm == BlackWins ||
12425         cm == GameIsDrawn || cm == GameUnfinished) {
12426         DisplayMessage("", _("No moves in game"));
12427         if (cmailMsgLoaded) {
12428             if (appData.debugMode)
12429               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12430             ClearHighlights();
12431             flipView = FALSE;
12432         }
12433         DrawPosition(FALSE, boards[currentMove]);
12434         DisplayBothClocks();
12435         gameMode = EditGame;
12436         ModeHighlight();
12437         gameFileFP = NULL;
12438         cmailOldMove = 0;
12439         return TRUE;
12440     }
12441
12442     // [HGM] PV info: routine tests if comment empty
12443     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12444         DisplayComment(currentMove - 1, commentList[currentMove]);
12445     }
12446     if (!matchMode && appData.timeDelay != 0)
12447       DrawPosition(FALSE, boards[currentMove]);
12448
12449     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12450       programStats.ok_to_send = 1;
12451     }
12452
12453     /* if the first token after the PGN tags is a move
12454      * and not move number 1, retrieve it from the parser
12455      */
12456     if (cm != MoveNumberOne)
12457         LoadGameOneMove(cm);
12458
12459     /* load the remaining moves from the file */
12460     while (LoadGameOneMove(EndOfFile)) {
12461       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12462       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12463     }
12464
12465     /* rewind to the start of the game */
12466     currentMove = backwardMostMove;
12467
12468     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12469
12470     if (oldGameMode == AnalyzeFile) {
12471       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12472       AnalyzeFileEvent();
12473     } else
12474     if (oldGameMode == AnalyzeMode) {
12475       AnalyzeFileEvent();
12476     }
12477
12478     if(creatingBook) return TRUE;
12479     if (!matchMode && pos > 0) {
12480         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12481     } else
12482     if (matchMode || appData.timeDelay == 0) {
12483       ToEndEvent();
12484     } else if (appData.timeDelay > 0) {
12485       AutoPlayGameLoop();
12486     }
12487
12488     if (appData.debugMode)
12489         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12490
12491     loadFlag = 0; /* [HGM] true game starts */
12492     return TRUE;
12493 }
12494
12495 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12496 int
12497 ReloadPosition (int offset)
12498 {
12499     int positionNumber = lastLoadPositionNumber + offset;
12500     if (lastLoadPositionFP == NULL) {
12501         DisplayError(_("No position has been loaded yet"), 0);
12502         return FALSE;
12503     }
12504     if (positionNumber <= 0) {
12505         DisplayError(_("Can't back up any further"), 0);
12506         return FALSE;
12507     }
12508     return LoadPosition(lastLoadPositionFP, positionNumber,
12509                         lastLoadPositionTitle);
12510 }
12511
12512 /* Load the nth position from the given file */
12513 int
12514 LoadPositionFromFile (char *filename, int n, char *title)
12515 {
12516     FILE *f;
12517     char buf[MSG_SIZ];
12518
12519     if (strcmp(filename, "-") == 0) {
12520         return LoadPosition(stdin, n, "stdin");
12521     } else {
12522         f = fopen(filename, "rb");
12523         if (f == NULL) {
12524             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12525             DisplayError(buf, errno);
12526             return FALSE;
12527         } else {
12528             return LoadPosition(f, n, title);
12529         }
12530     }
12531 }
12532
12533 /* Load the nth position from the given open file, and close it */
12534 int
12535 LoadPosition (FILE *f, int positionNumber, char *title)
12536 {
12537     char *p, line[MSG_SIZ];
12538     Board initial_position;
12539     int i, j, fenMode, pn;
12540
12541     if (gameMode == Training )
12542         SetTrainingModeOff();
12543
12544     if (gameMode != BeginningOfGame) {
12545         Reset(FALSE, TRUE);
12546     }
12547     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12548         fclose(lastLoadPositionFP);
12549     }
12550     if (positionNumber == 0) positionNumber = 1;
12551     lastLoadPositionFP = f;
12552     lastLoadPositionNumber = positionNumber;
12553     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12554     if (first.pr == NoProc && !appData.noChessProgram) {
12555       StartChessProgram(&first);
12556       InitChessProgram(&first, FALSE);
12557     }
12558     pn = positionNumber;
12559     if (positionNumber < 0) {
12560         /* Negative position number means to seek to that byte offset */
12561         if (fseek(f, -positionNumber, 0) == -1) {
12562             DisplayError(_("Can't seek on position file"), 0);
12563             return FALSE;
12564         };
12565         pn = 1;
12566     } else {
12567         if (fseek(f, 0, 0) == -1) {
12568             if (f == lastLoadPositionFP ?
12569                 positionNumber == lastLoadPositionNumber + 1 :
12570                 positionNumber == 1) {
12571                 pn = 1;
12572             } else {
12573                 DisplayError(_("Can't seek on position file"), 0);
12574                 return FALSE;
12575             }
12576         }
12577     }
12578     /* See if this file is FEN or old-style xboard */
12579     if (fgets(line, MSG_SIZ, f) == NULL) {
12580         DisplayError(_("Position not found in file"), 0);
12581         return FALSE;
12582     }
12583     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12584     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12585
12586     if (pn >= 2) {
12587         if (fenMode || line[0] == '#') pn--;
12588         while (pn > 0) {
12589             /* skip positions before number pn */
12590             if (fgets(line, MSG_SIZ, f) == NULL) {
12591                 Reset(TRUE, TRUE);
12592                 DisplayError(_("Position not found in file"), 0);
12593                 return FALSE;
12594             }
12595             if (fenMode || line[0] == '#') pn--;
12596         }
12597     }
12598
12599     if (fenMode) {
12600         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12601             DisplayError(_("Bad FEN position in file"), 0);
12602             return FALSE;
12603         }
12604     } else {
12605         (void) fgets(line, MSG_SIZ, f);
12606         (void) fgets(line, MSG_SIZ, f);
12607
12608         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12609             (void) fgets(line, MSG_SIZ, f);
12610             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12611                 if (*p == ' ')
12612                   continue;
12613                 initial_position[i][j++] = CharToPiece(*p);
12614             }
12615         }
12616
12617         blackPlaysFirst = FALSE;
12618         if (!feof(f)) {
12619             (void) fgets(line, MSG_SIZ, f);
12620             if (strncmp(line, "black", strlen("black"))==0)
12621               blackPlaysFirst = TRUE;
12622         }
12623     }
12624     startedFromSetupPosition = TRUE;
12625
12626     CopyBoard(boards[0], initial_position);
12627     if (blackPlaysFirst) {
12628         currentMove = forwardMostMove = backwardMostMove = 1;
12629         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12630         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12631         CopyBoard(boards[1], initial_position);
12632         DisplayMessage("", _("Black to play"));
12633     } else {
12634         currentMove = forwardMostMove = backwardMostMove = 0;
12635         DisplayMessage("", _("White to play"));
12636     }
12637     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12638     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12639         SendToProgram("force\n", &first);
12640         SendBoard(&first, forwardMostMove);
12641     }
12642     if (appData.debugMode) {
12643 int i, j;
12644   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12645   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12646         fprintf(debugFP, "Load Position\n");
12647     }
12648
12649     if (positionNumber > 1) {
12650       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12651         DisplayTitle(line);
12652     } else {
12653         DisplayTitle(title);
12654     }
12655     gameMode = EditGame;
12656     ModeHighlight();
12657     ResetClocks();
12658     timeRemaining[0][1] = whiteTimeRemaining;
12659     timeRemaining[1][1] = blackTimeRemaining;
12660     DrawPosition(FALSE, boards[currentMove]);
12661
12662     return TRUE;
12663 }
12664
12665
12666 void
12667 CopyPlayerNameIntoFileName (char **dest, char *src)
12668 {
12669     while (*src != NULLCHAR && *src != ',') {
12670         if (*src == ' ') {
12671             *(*dest)++ = '_';
12672             src++;
12673         } else {
12674             *(*dest)++ = *src++;
12675         }
12676     }
12677 }
12678
12679 char *
12680 DefaultFileName (char *ext)
12681 {
12682     static char def[MSG_SIZ];
12683     char *p;
12684
12685     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12686         p = def;
12687         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12688         *p++ = '-';
12689         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12690         *p++ = '.';
12691         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12692     } else {
12693         def[0] = NULLCHAR;
12694     }
12695     return def;
12696 }
12697
12698 /* Save the current game to the given file */
12699 int
12700 SaveGameToFile (char *filename, int append)
12701 {
12702     FILE *f;
12703     char buf[MSG_SIZ];
12704     int result, i, t,tot=0;
12705
12706     if (strcmp(filename, "-") == 0) {
12707         return SaveGame(stdout, 0, NULL);
12708     } else {
12709         for(i=0; i<10; i++) { // upto 10 tries
12710              f = fopen(filename, append ? "a" : "w");
12711              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12712              if(f || errno != 13) break;
12713              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12714              tot += t;
12715         }
12716         if (f == NULL) {
12717             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12718             DisplayError(buf, errno);
12719             return FALSE;
12720         } else {
12721             safeStrCpy(buf, lastMsg, MSG_SIZ);
12722             DisplayMessage(_("Waiting for access to save file"), "");
12723             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12724             DisplayMessage(_("Saving game"), "");
12725             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12726             result = SaveGame(f, 0, NULL);
12727             DisplayMessage(buf, "");
12728             return result;
12729         }
12730     }
12731 }
12732
12733 char *
12734 SavePart (char *str)
12735 {
12736     static char buf[MSG_SIZ];
12737     char *p;
12738
12739     p = strchr(str, ' ');
12740     if (p == NULL) return str;
12741     strncpy(buf, str, p - str);
12742     buf[p - str] = NULLCHAR;
12743     return buf;
12744 }
12745
12746 #define PGN_MAX_LINE 75
12747
12748 #define PGN_SIDE_WHITE  0
12749 #define PGN_SIDE_BLACK  1
12750
12751 static int
12752 FindFirstMoveOutOfBook (int side)
12753 {
12754     int result = -1;
12755
12756     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12757         int index = backwardMostMove;
12758         int has_book_hit = 0;
12759
12760         if( (index % 2) != side ) {
12761             index++;
12762         }
12763
12764         while( index < forwardMostMove ) {
12765             /* Check to see if engine is in book */
12766             int depth = pvInfoList[index].depth;
12767             int score = pvInfoList[index].score;
12768             int in_book = 0;
12769
12770             if( depth <= 2 ) {
12771                 in_book = 1;
12772             }
12773             else if( score == 0 && depth == 63 ) {
12774                 in_book = 1; /* Zappa */
12775             }
12776             else if( score == 2 && depth == 99 ) {
12777                 in_book = 1; /* Abrok */
12778             }
12779
12780             has_book_hit += in_book;
12781
12782             if( ! in_book ) {
12783                 result = index;
12784
12785                 break;
12786             }
12787
12788             index += 2;
12789         }
12790     }
12791
12792     return result;
12793 }
12794
12795 void
12796 GetOutOfBookInfo (char * buf)
12797 {
12798     int oob[2];
12799     int i;
12800     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12801
12802     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12803     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12804
12805     *buf = '\0';
12806
12807     if( oob[0] >= 0 || oob[1] >= 0 ) {
12808         for( i=0; i<2; i++ ) {
12809             int idx = oob[i];
12810
12811             if( idx >= 0 ) {
12812                 if( i > 0 && oob[0] >= 0 ) {
12813                     strcat( buf, "   " );
12814                 }
12815
12816                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12817                 sprintf( buf+strlen(buf), "%s%.2f",
12818                     pvInfoList[idx].score >= 0 ? "+" : "",
12819                     pvInfoList[idx].score / 100.0 );
12820             }
12821         }
12822     }
12823 }
12824
12825 /* Save game in PGN style and close the file */
12826 int
12827 SaveGamePGN (FILE *f)
12828 {
12829     int i, offset, linelen, newblock;
12830 //    char *movetext;
12831     char numtext[32];
12832     int movelen, numlen, blank;
12833     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12834
12835     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12836
12837     PrintPGNTags(f, &gameInfo);
12838
12839     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12840
12841     if (backwardMostMove > 0 || startedFromSetupPosition) {
12842         char *fen = PositionToFEN(backwardMostMove, NULL);
12843         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12844         fprintf(f, "\n{--------------\n");
12845         PrintPosition(f, backwardMostMove);
12846         fprintf(f, "--------------}\n");
12847         free(fen);
12848     }
12849     else {
12850         /* [AS] Out of book annotation */
12851         if( appData.saveOutOfBookInfo ) {
12852             char buf[64];
12853
12854             GetOutOfBookInfo( buf );
12855
12856             if( buf[0] != '\0' ) {
12857                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12858             }
12859         }
12860
12861         fprintf(f, "\n");
12862     }
12863
12864     i = backwardMostMove;
12865     linelen = 0;
12866     newblock = TRUE;
12867
12868     while (i < forwardMostMove) {
12869         /* Print comments preceding this move */
12870         if (commentList[i] != NULL) {
12871             if (linelen > 0) fprintf(f, "\n");
12872             fprintf(f, "%s", commentList[i]);
12873             linelen = 0;
12874             newblock = TRUE;
12875         }
12876
12877         /* Format move number */
12878         if ((i % 2) == 0)
12879           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12880         else
12881           if (newblock)
12882             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12883           else
12884             numtext[0] = NULLCHAR;
12885
12886         numlen = strlen(numtext);
12887         newblock = FALSE;
12888
12889         /* Print move number */
12890         blank = linelen > 0 && numlen > 0;
12891         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12892             fprintf(f, "\n");
12893             linelen = 0;
12894             blank = 0;
12895         }
12896         if (blank) {
12897             fprintf(f, " ");
12898             linelen++;
12899         }
12900         fprintf(f, "%s", numtext);
12901         linelen += numlen;
12902
12903         /* Get move */
12904         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12905         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12906
12907         /* Print move */
12908         blank = linelen > 0 && movelen > 0;
12909         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12910             fprintf(f, "\n");
12911             linelen = 0;
12912             blank = 0;
12913         }
12914         if (blank) {
12915             fprintf(f, " ");
12916             linelen++;
12917         }
12918         fprintf(f, "%s", move_buffer);
12919         linelen += movelen;
12920
12921         /* [AS] Add PV info if present */
12922         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12923             /* [HGM] add time */
12924             char buf[MSG_SIZ]; int seconds;
12925
12926             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12927
12928             if( seconds <= 0)
12929               buf[0] = 0;
12930             else
12931               if( seconds < 30 )
12932                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12933               else
12934                 {
12935                   seconds = (seconds + 4)/10; // round to full seconds
12936                   if( seconds < 60 )
12937                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12938                   else
12939                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12940                 }
12941
12942             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12943                       pvInfoList[i].score >= 0 ? "+" : "",
12944                       pvInfoList[i].score / 100.0,
12945                       pvInfoList[i].depth,
12946                       buf );
12947
12948             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12949
12950             /* Print score/depth */
12951             blank = linelen > 0 && movelen > 0;
12952             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12953                 fprintf(f, "\n");
12954                 linelen = 0;
12955                 blank = 0;
12956             }
12957             if (blank) {
12958                 fprintf(f, " ");
12959                 linelen++;
12960             }
12961             fprintf(f, "%s", move_buffer);
12962             linelen += movelen;
12963         }
12964
12965         i++;
12966     }
12967
12968     /* Start a new line */
12969     if (linelen > 0) fprintf(f, "\n");
12970
12971     /* Print comments after last move */
12972     if (commentList[i] != NULL) {
12973         fprintf(f, "%s\n", commentList[i]);
12974     }
12975
12976     /* Print result */
12977     if (gameInfo.resultDetails != NULL &&
12978         gameInfo.resultDetails[0] != NULLCHAR) {
12979         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12980                 PGNResult(gameInfo.result));
12981     } else {
12982         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12983     }
12984
12985     fclose(f);
12986     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12987     return TRUE;
12988 }
12989
12990 /* Save game in old style and close the file */
12991 int
12992 SaveGameOldStyle (FILE *f)
12993 {
12994     int i, offset;
12995     time_t tm;
12996
12997     tm = time((time_t *) NULL);
12998
12999     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13000     PrintOpponents(f);
13001
13002     if (backwardMostMove > 0 || startedFromSetupPosition) {
13003         fprintf(f, "\n[--------------\n");
13004         PrintPosition(f, backwardMostMove);
13005         fprintf(f, "--------------]\n");
13006     } else {
13007         fprintf(f, "\n");
13008     }
13009
13010     i = backwardMostMove;
13011     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13012
13013     while (i < forwardMostMove) {
13014         if (commentList[i] != NULL) {
13015             fprintf(f, "[%s]\n", commentList[i]);
13016         }
13017
13018         if ((i % 2) == 1) {
13019             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13020             i++;
13021         } else {
13022             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13023             i++;
13024             if (commentList[i] != NULL) {
13025                 fprintf(f, "\n");
13026                 continue;
13027             }
13028             if (i >= forwardMostMove) {
13029                 fprintf(f, "\n");
13030                 break;
13031             }
13032             fprintf(f, "%s\n", parseList[i]);
13033             i++;
13034         }
13035     }
13036
13037     if (commentList[i] != NULL) {
13038         fprintf(f, "[%s]\n", commentList[i]);
13039     }
13040
13041     /* This isn't really the old style, but it's close enough */
13042     if (gameInfo.resultDetails != NULL &&
13043         gameInfo.resultDetails[0] != NULLCHAR) {
13044         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13045                 gameInfo.resultDetails);
13046     } else {
13047         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13048     }
13049
13050     fclose(f);
13051     return TRUE;
13052 }
13053
13054 /* Save the current game to open file f and close the file */
13055 int
13056 SaveGame (FILE *f, int dummy, char *dummy2)
13057 {
13058     if (gameMode == EditPosition) EditPositionDone(TRUE);
13059     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13060     if (appData.oldSaveStyle)
13061       return SaveGameOldStyle(f);
13062     else
13063       return SaveGamePGN(f);
13064 }
13065
13066 /* Save the current position to the given file */
13067 int
13068 SavePositionToFile (char *filename)
13069 {
13070     FILE *f;
13071     char buf[MSG_SIZ];
13072
13073     if (strcmp(filename, "-") == 0) {
13074         return SavePosition(stdout, 0, NULL);
13075     } else {
13076         f = fopen(filename, "a");
13077         if (f == NULL) {
13078             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13079             DisplayError(buf, errno);
13080             return FALSE;
13081         } else {
13082             safeStrCpy(buf, lastMsg, MSG_SIZ);
13083             DisplayMessage(_("Waiting for access to save file"), "");
13084             flock(fileno(f), LOCK_EX); // [HGM] lock
13085             DisplayMessage(_("Saving position"), "");
13086             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13087             SavePosition(f, 0, NULL);
13088             DisplayMessage(buf, "");
13089             return TRUE;
13090         }
13091     }
13092 }
13093
13094 /* Save the current position to the given open file and close the file */
13095 int
13096 SavePosition (FILE *f, int dummy, char *dummy2)
13097 {
13098     time_t tm;
13099     char *fen;
13100
13101     if (gameMode == EditPosition) EditPositionDone(TRUE);
13102     if (appData.oldSaveStyle) {
13103         tm = time((time_t *) NULL);
13104
13105         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13106         PrintOpponents(f);
13107         fprintf(f, "[--------------\n");
13108         PrintPosition(f, currentMove);
13109         fprintf(f, "--------------]\n");
13110     } else {
13111         fen = PositionToFEN(currentMove, NULL);
13112         fprintf(f, "%s\n", fen);
13113         free(fen);
13114     }
13115     fclose(f);
13116     return TRUE;
13117 }
13118
13119 void
13120 ReloadCmailMsgEvent (int unregister)
13121 {
13122 #if !WIN32
13123     static char *inFilename = NULL;
13124     static char *outFilename;
13125     int i;
13126     struct stat inbuf, outbuf;
13127     int status;
13128
13129     /* Any registered moves are unregistered if unregister is set, */
13130     /* i.e. invoked by the signal handler */
13131     if (unregister) {
13132         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13133             cmailMoveRegistered[i] = FALSE;
13134             if (cmailCommentList[i] != NULL) {
13135                 free(cmailCommentList[i]);
13136                 cmailCommentList[i] = NULL;
13137             }
13138         }
13139         nCmailMovesRegistered = 0;
13140     }
13141
13142     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13143         cmailResult[i] = CMAIL_NOT_RESULT;
13144     }
13145     nCmailResults = 0;
13146
13147     if (inFilename == NULL) {
13148         /* Because the filenames are static they only get malloced once  */
13149         /* and they never get freed                                      */
13150         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13151         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13152
13153         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13154         sprintf(outFilename, "%s.out", appData.cmailGameName);
13155     }
13156
13157     status = stat(outFilename, &outbuf);
13158     if (status < 0) {
13159         cmailMailedMove = FALSE;
13160     } else {
13161         status = stat(inFilename, &inbuf);
13162         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13163     }
13164
13165     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13166        counts the games, notes how each one terminated, etc.
13167
13168        It would be nice to remove this kludge and instead gather all
13169        the information while building the game list.  (And to keep it
13170        in the game list nodes instead of having a bunch of fixed-size
13171        parallel arrays.)  Note this will require getting each game's
13172        termination from the PGN tags, as the game list builder does
13173        not process the game moves.  --mann
13174        */
13175     cmailMsgLoaded = TRUE;
13176     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13177
13178     /* Load first game in the file or popup game menu */
13179     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13180
13181 #endif /* !WIN32 */
13182     return;
13183 }
13184
13185 int
13186 RegisterMove ()
13187 {
13188     FILE *f;
13189     char string[MSG_SIZ];
13190
13191     if (   cmailMailedMove
13192         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13193         return TRUE;            /* Allow free viewing  */
13194     }
13195
13196     /* Unregister move to ensure that we don't leave RegisterMove        */
13197     /* with the move registered when the conditions for registering no   */
13198     /* longer hold                                                       */
13199     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13200         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13201         nCmailMovesRegistered --;
13202
13203         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13204           {
13205               free(cmailCommentList[lastLoadGameNumber - 1]);
13206               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13207           }
13208     }
13209
13210     if (cmailOldMove == -1) {
13211         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13212         return FALSE;
13213     }
13214
13215     if (currentMove > cmailOldMove + 1) {
13216         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13217         return FALSE;
13218     }
13219
13220     if (currentMove < cmailOldMove) {
13221         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13222         return FALSE;
13223     }
13224
13225     if (forwardMostMove > currentMove) {
13226         /* Silently truncate extra moves */
13227         TruncateGame();
13228     }
13229
13230     if (   (currentMove == cmailOldMove + 1)
13231         || (   (currentMove == cmailOldMove)
13232             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13233                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13234         if (gameInfo.result != GameUnfinished) {
13235             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13236         }
13237
13238         if (commentList[currentMove] != NULL) {
13239             cmailCommentList[lastLoadGameNumber - 1]
13240               = StrSave(commentList[currentMove]);
13241         }
13242         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13243
13244         if (appData.debugMode)
13245           fprintf(debugFP, "Saving %s for game %d\n",
13246                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13247
13248         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13249
13250         f = fopen(string, "w");
13251         if (appData.oldSaveStyle) {
13252             SaveGameOldStyle(f); /* also closes the file */
13253
13254             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13255             f = fopen(string, "w");
13256             SavePosition(f, 0, NULL); /* also closes the file */
13257         } else {
13258             fprintf(f, "{--------------\n");
13259             PrintPosition(f, currentMove);
13260             fprintf(f, "--------------}\n\n");
13261
13262             SaveGame(f, 0, NULL); /* also closes the file*/
13263         }
13264
13265         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13266         nCmailMovesRegistered ++;
13267     } else if (nCmailGames == 1) {
13268         DisplayError(_("You have not made a move yet"), 0);
13269         return FALSE;
13270     }
13271
13272     return TRUE;
13273 }
13274
13275 void
13276 MailMoveEvent ()
13277 {
13278 #if !WIN32
13279     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13280     FILE *commandOutput;
13281     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13282     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13283     int nBuffers;
13284     int i;
13285     int archived;
13286     char *arcDir;
13287
13288     if (! cmailMsgLoaded) {
13289         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13290         return;
13291     }
13292
13293     if (nCmailGames == nCmailResults) {
13294         DisplayError(_("No unfinished games"), 0);
13295         return;
13296     }
13297
13298 #if CMAIL_PROHIBIT_REMAIL
13299     if (cmailMailedMove) {
13300       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);
13301         DisplayError(msg, 0);
13302         return;
13303     }
13304 #endif
13305
13306     if (! (cmailMailedMove || RegisterMove())) return;
13307
13308     if (   cmailMailedMove
13309         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13310       snprintf(string, MSG_SIZ, partCommandString,
13311                appData.debugMode ? " -v" : "", appData.cmailGameName);
13312         commandOutput = popen(string, "r");
13313
13314         if (commandOutput == NULL) {
13315             DisplayError(_("Failed to invoke cmail"), 0);
13316         } else {
13317             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13318                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13319             }
13320             if (nBuffers > 1) {
13321                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13322                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13323                 nBytes = MSG_SIZ - 1;
13324             } else {
13325                 (void) memcpy(msg, buffer, nBytes);
13326             }
13327             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13328
13329             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13330                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13331
13332                 archived = TRUE;
13333                 for (i = 0; i < nCmailGames; i ++) {
13334                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13335                         archived = FALSE;
13336                     }
13337                 }
13338                 if (   archived
13339                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13340                         != NULL)) {
13341                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13342                            arcDir,
13343                            appData.cmailGameName,
13344                            gameInfo.date);
13345                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13346                     cmailMsgLoaded = FALSE;
13347                 }
13348             }
13349
13350             DisplayInformation(msg);
13351             pclose(commandOutput);
13352         }
13353     } else {
13354         if ((*cmailMsg) != '\0') {
13355             DisplayInformation(cmailMsg);
13356         }
13357     }
13358
13359     return;
13360 #endif /* !WIN32 */
13361 }
13362
13363 char *
13364 CmailMsg ()
13365 {
13366 #if WIN32
13367     return NULL;
13368 #else
13369     int  prependComma = 0;
13370     char number[5];
13371     char string[MSG_SIZ];       /* Space for game-list */
13372     int  i;
13373
13374     if (!cmailMsgLoaded) return "";
13375
13376     if (cmailMailedMove) {
13377       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13378     } else {
13379         /* Create a list of games left */
13380       snprintf(string, MSG_SIZ, "[");
13381         for (i = 0; i < nCmailGames; i ++) {
13382             if (! (   cmailMoveRegistered[i]
13383                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13384                 if (prependComma) {
13385                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13386                 } else {
13387                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13388                     prependComma = 1;
13389                 }
13390
13391                 strcat(string, number);
13392             }
13393         }
13394         strcat(string, "]");
13395
13396         if (nCmailMovesRegistered + nCmailResults == 0) {
13397             switch (nCmailGames) {
13398               case 1:
13399                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13400                 break;
13401
13402               case 2:
13403                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13404                 break;
13405
13406               default:
13407                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13408                          nCmailGames);
13409                 break;
13410             }
13411         } else {
13412             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13413               case 1:
13414                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13415                          string);
13416                 break;
13417
13418               case 0:
13419                 if (nCmailResults == nCmailGames) {
13420                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13421                 } else {
13422                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13423                 }
13424                 break;
13425
13426               default:
13427                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13428                          string);
13429             }
13430         }
13431     }
13432     return cmailMsg;
13433 #endif /* WIN32 */
13434 }
13435
13436 void
13437 ResetGameEvent ()
13438 {
13439     if (gameMode == Training)
13440       SetTrainingModeOff();
13441
13442     Reset(TRUE, TRUE);
13443     cmailMsgLoaded = FALSE;
13444     if (appData.icsActive) {
13445       SendToICS(ics_prefix);
13446       SendToICS("refresh\n");
13447     }
13448 }
13449
13450 void
13451 ExitEvent (int status)
13452 {
13453     exiting++;
13454     if (exiting > 2) {
13455       /* Give up on clean exit */
13456       exit(status);
13457     }
13458     if (exiting > 1) {
13459       /* Keep trying for clean exit */
13460       return;
13461     }
13462
13463     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13464
13465     if (telnetISR != NULL) {
13466       RemoveInputSource(telnetISR);
13467     }
13468     if (icsPR != NoProc) {
13469       DestroyChildProcess(icsPR, TRUE);
13470     }
13471
13472     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13473     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13474
13475     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13476     /* make sure this other one finishes before killing it!                  */
13477     if(endingGame) { int count = 0;
13478         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13479         while(endingGame && count++ < 10) DoSleep(1);
13480         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13481     }
13482
13483     /* Kill off chess programs */
13484     if (first.pr != NoProc) {
13485         ExitAnalyzeMode();
13486
13487         DoSleep( appData.delayBeforeQuit );
13488         SendToProgram("quit\n", &first);
13489         DoSleep( appData.delayAfterQuit );
13490         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13491     }
13492     if (second.pr != NoProc) {
13493         DoSleep( appData.delayBeforeQuit );
13494         SendToProgram("quit\n", &second);
13495         DoSleep( appData.delayAfterQuit );
13496         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13497     }
13498     if (first.isr != NULL) {
13499         RemoveInputSource(first.isr);
13500     }
13501     if (second.isr != NULL) {
13502         RemoveInputSource(second.isr);
13503     }
13504
13505     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13506     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13507
13508     ShutDownFrontEnd();
13509     exit(status);
13510 }
13511
13512 void
13513 PauseEngine (ChessProgramState *cps)
13514 {
13515     SendToProgram("pause\n", cps);
13516     cps->pause = 2;
13517 }
13518
13519 void
13520 UnPauseEngine (ChessProgramState *cps)
13521 {
13522     SendToProgram("resume\n", cps);
13523     cps->pause = 1;
13524 }
13525
13526 void
13527 PauseEvent ()
13528 {
13529     if (appData.debugMode)
13530         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13531     if (pausing) {
13532         pausing = FALSE;
13533         ModeHighlight();
13534         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13535             StartClocks();
13536             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13537                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13538                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13539             }
13540             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13541             HandleMachineMove(stashedInputMove, stalledEngine);
13542             stalledEngine = NULL;
13543             return;
13544         }
13545         if (gameMode == MachinePlaysWhite ||
13546             gameMode == TwoMachinesPlay   ||
13547             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13548             if(first.pause)  UnPauseEngine(&first);
13549             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13550             if(second.pause) UnPauseEngine(&second);
13551             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13552             StartClocks();
13553         } else {
13554             DisplayBothClocks();
13555         }
13556         if (gameMode == PlayFromGameFile) {
13557             if (appData.timeDelay >= 0)
13558                 AutoPlayGameLoop();
13559         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13560             Reset(FALSE, TRUE);
13561             SendToICS(ics_prefix);
13562             SendToICS("refresh\n");
13563         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13564             ForwardInner(forwardMostMove);
13565         }
13566         pauseExamInvalid = FALSE;
13567     } else {
13568         switch (gameMode) {
13569           default:
13570             return;
13571           case IcsExamining:
13572             pauseExamForwardMostMove = forwardMostMove;
13573             pauseExamInvalid = FALSE;
13574             /* fall through */
13575           case IcsObserving:
13576           case IcsPlayingWhite:
13577           case IcsPlayingBlack:
13578             pausing = TRUE;
13579             ModeHighlight();
13580             return;
13581           case PlayFromGameFile:
13582             (void) StopLoadGameTimer();
13583             pausing = TRUE;
13584             ModeHighlight();
13585             break;
13586           case BeginningOfGame:
13587             if (appData.icsActive) return;
13588             /* else fall through */
13589           case MachinePlaysWhite:
13590           case MachinePlaysBlack:
13591           case TwoMachinesPlay:
13592             if (forwardMostMove == 0)
13593               return;           /* don't pause if no one has moved */
13594             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13595                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13596                 if(onMove->pause) {           // thinking engine can be paused
13597                     PauseEngine(onMove);      // do it
13598                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13599                         PauseEngine(onMove->other);
13600                     else
13601                         SendToProgram("easy\n", onMove->other);
13602                     StopClocks();
13603                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13604             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13605                 if(first.pause) {
13606                     PauseEngine(&first);
13607                     StopClocks();
13608                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13609             } else { // human on move, pause pondering by either method
13610                 if(first.pause)
13611                     PauseEngine(&first);
13612                 else if(appData.ponderNextMove)
13613                     SendToProgram("easy\n", &first);
13614                 StopClocks();
13615             }
13616             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13617           case AnalyzeMode:
13618             pausing = TRUE;
13619             ModeHighlight();
13620             break;
13621         }
13622     }
13623 }
13624
13625 void
13626 EditCommentEvent ()
13627 {
13628     char title[MSG_SIZ];
13629
13630     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13631       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13632     } else {
13633       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13634                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13635                parseList[currentMove - 1]);
13636     }
13637
13638     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13639 }
13640
13641
13642 void
13643 EditTagsEvent ()
13644 {
13645     char *tags = PGNTags(&gameInfo);
13646     bookUp = FALSE;
13647     EditTagsPopUp(tags, NULL);
13648     free(tags);
13649 }
13650
13651 void
13652 ToggleSecond ()
13653 {
13654   if(second.analyzing) {
13655     SendToProgram("exit\n", &second);
13656     second.analyzing = FALSE;
13657   } else {
13658     if (second.pr == NoProc) StartChessProgram(&second);
13659     InitChessProgram(&second, FALSE);
13660     FeedMovesToProgram(&second, currentMove);
13661
13662     SendToProgram("analyze\n", &second);
13663     second.analyzing = TRUE;
13664   }
13665 }
13666
13667 /* Toggle ShowThinking */
13668 void
13669 ToggleShowThinking()
13670 {
13671   appData.showThinking = !appData.showThinking;
13672   ShowThinkingEvent();
13673 }
13674
13675 int
13676 AnalyzeModeEvent ()
13677 {
13678     char buf[MSG_SIZ];
13679
13680     if (!first.analysisSupport) {
13681       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13682       DisplayError(buf, 0);
13683       return 0;
13684     }
13685     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13686     if (appData.icsActive) {
13687         if (gameMode != IcsObserving) {
13688           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13689             DisplayError(buf, 0);
13690             /* secure check */
13691             if (appData.icsEngineAnalyze) {
13692                 if (appData.debugMode)
13693                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13694                 ExitAnalyzeMode();
13695                 ModeHighlight();
13696             }
13697             return 0;
13698         }
13699         /* if enable, user wants to disable icsEngineAnalyze */
13700         if (appData.icsEngineAnalyze) {
13701                 ExitAnalyzeMode();
13702                 ModeHighlight();
13703                 return 0;
13704         }
13705         appData.icsEngineAnalyze = TRUE;
13706         if (appData.debugMode)
13707             fprintf(debugFP, "ICS engine analyze starting... \n");
13708     }
13709
13710     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13711     if (appData.noChessProgram || gameMode == AnalyzeMode)
13712       return 0;
13713
13714     if (gameMode != AnalyzeFile) {
13715         if (!appData.icsEngineAnalyze) {
13716                EditGameEvent();
13717                if (gameMode != EditGame) return 0;
13718         }
13719         if (!appData.showThinking) ToggleShowThinking();
13720         ResurrectChessProgram();
13721         SendToProgram("analyze\n", &first);
13722         first.analyzing = TRUE;
13723         /*first.maybeThinking = TRUE;*/
13724         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13725         EngineOutputPopUp();
13726     }
13727     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13728     pausing = FALSE;
13729     ModeHighlight();
13730     SetGameInfo();
13731
13732     StartAnalysisClock();
13733     GetTimeMark(&lastNodeCountTime);
13734     lastNodeCount = 0;
13735     return 1;
13736 }
13737
13738 void
13739 AnalyzeFileEvent ()
13740 {
13741     if (appData.noChessProgram || gameMode == AnalyzeFile)
13742       return;
13743
13744     if (!first.analysisSupport) {
13745       char buf[MSG_SIZ];
13746       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13747       DisplayError(buf, 0);
13748       return;
13749     }
13750
13751     if (gameMode != AnalyzeMode) {
13752         keepInfo = 1; // mere annotating should not alter PGN tags
13753         EditGameEvent();
13754         keepInfo = 0;
13755         if (gameMode != EditGame) return;
13756         if (!appData.showThinking) ToggleShowThinking();
13757         ResurrectChessProgram();
13758         SendToProgram("analyze\n", &first);
13759         first.analyzing = TRUE;
13760         /*first.maybeThinking = TRUE;*/
13761         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13762         EngineOutputPopUp();
13763     }
13764     gameMode = AnalyzeFile;
13765     pausing = FALSE;
13766     ModeHighlight();
13767
13768     StartAnalysisClock();
13769     GetTimeMark(&lastNodeCountTime);
13770     lastNodeCount = 0;
13771     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13772     AnalysisPeriodicEvent(1);
13773 }
13774
13775 void
13776 MachineWhiteEvent ()
13777 {
13778     char buf[MSG_SIZ];
13779     char *bookHit = NULL;
13780
13781     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13782       return;
13783
13784
13785     if (gameMode == PlayFromGameFile ||
13786         gameMode == TwoMachinesPlay  ||
13787         gameMode == Training         ||
13788         gameMode == AnalyzeMode      ||
13789         gameMode == EndOfGame)
13790         EditGameEvent();
13791
13792     if (gameMode == EditPosition)
13793         EditPositionDone(TRUE);
13794
13795     if (!WhiteOnMove(currentMove)) {
13796         DisplayError(_("It is not White's turn"), 0);
13797         return;
13798     }
13799
13800     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13801       ExitAnalyzeMode();
13802
13803     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13804         gameMode == AnalyzeFile)
13805         TruncateGame();
13806
13807     ResurrectChessProgram();    /* in case it isn't running */
13808     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13809         gameMode = MachinePlaysWhite;
13810         ResetClocks();
13811     } else
13812     gameMode = MachinePlaysWhite;
13813     pausing = FALSE;
13814     ModeHighlight();
13815     SetGameInfo();
13816     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13817     DisplayTitle(buf);
13818     if (first.sendName) {
13819       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13820       SendToProgram(buf, &first);
13821     }
13822     if (first.sendTime) {
13823       if (first.useColors) {
13824         SendToProgram("black\n", &first); /*gnu kludge*/
13825       }
13826       SendTimeRemaining(&first, TRUE);
13827     }
13828     if (first.useColors) {
13829       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13830     }
13831     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13832     SetMachineThinkingEnables();
13833     first.maybeThinking = TRUE;
13834     StartClocks();
13835     firstMove = FALSE;
13836
13837     if (appData.autoFlipView && !flipView) {
13838       flipView = !flipView;
13839       DrawPosition(FALSE, NULL);
13840       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13841     }
13842
13843     if(bookHit) { // [HGM] book: simulate book reply
13844         static char bookMove[MSG_SIZ]; // a bit generous?
13845
13846         programStats.nodes = programStats.depth = programStats.time =
13847         programStats.score = programStats.got_only_move = 0;
13848         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13849
13850         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13851         strcat(bookMove, bookHit);
13852         HandleMachineMove(bookMove, &first);
13853     }
13854 }
13855
13856 void
13857 MachineBlackEvent ()
13858 {
13859   char buf[MSG_SIZ];
13860   char *bookHit = NULL;
13861
13862     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13863         return;
13864
13865
13866     if (gameMode == PlayFromGameFile ||
13867         gameMode == TwoMachinesPlay  ||
13868         gameMode == Training         ||
13869         gameMode == AnalyzeMode      ||
13870         gameMode == EndOfGame)
13871         EditGameEvent();
13872
13873     if (gameMode == EditPosition)
13874         EditPositionDone(TRUE);
13875
13876     if (WhiteOnMove(currentMove)) {
13877         DisplayError(_("It is not Black's turn"), 0);
13878         return;
13879     }
13880
13881     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13882       ExitAnalyzeMode();
13883
13884     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13885         gameMode == AnalyzeFile)
13886         TruncateGame();
13887
13888     ResurrectChessProgram();    /* in case it isn't running */
13889     gameMode = MachinePlaysBlack;
13890     pausing = FALSE;
13891     ModeHighlight();
13892     SetGameInfo();
13893     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13894     DisplayTitle(buf);
13895     if (first.sendName) {
13896       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13897       SendToProgram(buf, &first);
13898     }
13899     if (first.sendTime) {
13900       if (first.useColors) {
13901         SendToProgram("white\n", &first); /*gnu kludge*/
13902       }
13903       SendTimeRemaining(&first, FALSE);
13904     }
13905     if (first.useColors) {
13906       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13907     }
13908     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13909     SetMachineThinkingEnables();
13910     first.maybeThinking = TRUE;
13911     StartClocks();
13912
13913     if (appData.autoFlipView && flipView) {
13914       flipView = !flipView;
13915       DrawPosition(FALSE, NULL);
13916       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13917     }
13918     if(bookHit) { // [HGM] book: simulate book reply
13919         static char bookMove[MSG_SIZ]; // a bit generous?
13920
13921         programStats.nodes = programStats.depth = programStats.time =
13922         programStats.score = programStats.got_only_move = 0;
13923         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13924
13925         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13926         strcat(bookMove, bookHit);
13927         HandleMachineMove(bookMove, &first);
13928     }
13929 }
13930
13931
13932 void
13933 DisplayTwoMachinesTitle ()
13934 {
13935     char buf[MSG_SIZ];
13936     if (appData.matchGames > 0) {
13937         if(appData.tourneyFile[0]) {
13938           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13939                    gameInfo.white, _("vs."), gameInfo.black,
13940                    nextGame+1, appData.matchGames+1,
13941                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13942         } else
13943         if (first.twoMachinesColor[0] == 'w') {
13944           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13945                    gameInfo.white, _("vs."),  gameInfo.black,
13946                    first.matchWins, second.matchWins,
13947                    matchGame - 1 - (first.matchWins + second.matchWins));
13948         } else {
13949           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13950                    gameInfo.white, _("vs."), gameInfo.black,
13951                    second.matchWins, first.matchWins,
13952                    matchGame - 1 - (first.matchWins + second.matchWins));
13953         }
13954     } else {
13955       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13956     }
13957     DisplayTitle(buf);
13958 }
13959
13960 void
13961 SettingsMenuIfReady ()
13962 {
13963   if (second.lastPing != second.lastPong) {
13964     DisplayMessage("", _("Waiting for second chess program"));
13965     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13966     return;
13967   }
13968   ThawUI();
13969   DisplayMessage("", "");
13970   SettingsPopUp(&second);
13971 }
13972
13973 int
13974 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13975 {
13976     char buf[MSG_SIZ];
13977     if (cps->pr == NoProc) {
13978         StartChessProgram(cps);
13979         if (cps->protocolVersion == 1) {
13980           retry();
13981           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13982         } else {
13983           /* kludge: allow timeout for initial "feature" command */
13984           if(retry != TwoMachinesEventIfReady) FreezeUI();
13985           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13986           DisplayMessage("", buf);
13987           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13988         }
13989         return 1;
13990     }
13991     return 0;
13992 }
13993
13994 void
13995 TwoMachinesEvent P((void))
13996 {
13997     int i;
13998     char buf[MSG_SIZ];
13999     ChessProgramState *onmove;
14000     char *bookHit = NULL;
14001     static int stalling = 0;
14002     TimeMark now;
14003     long wait;
14004
14005     if (appData.noChessProgram) return;
14006
14007     switch (gameMode) {
14008       case TwoMachinesPlay:
14009         return;
14010       case MachinePlaysWhite:
14011       case MachinePlaysBlack:
14012         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14013             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14014             return;
14015         }
14016         /* fall through */
14017       case BeginningOfGame:
14018       case PlayFromGameFile:
14019       case EndOfGame:
14020         EditGameEvent();
14021         if (gameMode != EditGame) return;
14022         break;
14023       case EditPosition:
14024         EditPositionDone(TRUE);
14025         break;
14026       case AnalyzeMode:
14027       case AnalyzeFile:
14028         ExitAnalyzeMode();
14029         break;
14030       case EditGame:
14031       default:
14032         break;
14033     }
14034
14035 //    forwardMostMove = currentMove;
14036     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14037     startingEngine = TRUE;
14038
14039     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14040
14041     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14042     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14043       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14044       return;
14045     }
14046     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14047
14048     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14049         startingEngine = FALSE;
14050         DisplayError("second engine does not play this", 0);
14051         return;
14052     }
14053
14054     if(!stalling) {
14055       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14056       SendToProgram("force\n", &second);
14057       stalling = 1;
14058       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14059       return;
14060     }
14061     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14062     if(appData.matchPause>10000 || appData.matchPause<10)
14063                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14064     wait = SubtractTimeMarks(&now, &pauseStart);
14065     if(wait < appData.matchPause) {
14066         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14067         return;
14068     }
14069     // we are now committed to starting the game
14070     stalling = 0;
14071     DisplayMessage("", "");
14072     if (startedFromSetupPosition) {
14073         SendBoard(&second, backwardMostMove);
14074     if (appData.debugMode) {
14075         fprintf(debugFP, "Two Machines\n");
14076     }
14077     }
14078     for (i = backwardMostMove; i < forwardMostMove; i++) {
14079         SendMoveToProgram(i, &second);
14080     }
14081
14082     gameMode = TwoMachinesPlay;
14083     pausing = startingEngine = FALSE;
14084     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14085     SetGameInfo();
14086     DisplayTwoMachinesTitle();
14087     firstMove = TRUE;
14088     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14089         onmove = &first;
14090     } else {
14091         onmove = &second;
14092     }
14093     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14094     SendToProgram(first.computerString, &first);
14095     if (first.sendName) {
14096       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14097       SendToProgram(buf, &first);
14098     }
14099     SendToProgram(second.computerString, &second);
14100     if (second.sendName) {
14101       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14102       SendToProgram(buf, &second);
14103     }
14104
14105     ResetClocks();
14106     if (!first.sendTime || !second.sendTime) {
14107         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14108         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14109     }
14110     if (onmove->sendTime) {
14111       if (onmove->useColors) {
14112         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14113       }
14114       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14115     }
14116     if (onmove->useColors) {
14117       SendToProgram(onmove->twoMachinesColor, onmove);
14118     }
14119     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14120 //    SendToProgram("go\n", onmove);
14121     onmove->maybeThinking = TRUE;
14122     SetMachineThinkingEnables();
14123
14124     StartClocks();
14125
14126     if(bookHit) { // [HGM] book: simulate book reply
14127         static char bookMove[MSG_SIZ]; // a bit generous?
14128
14129         programStats.nodes = programStats.depth = programStats.time =
14130         programStats.score = programStats.got_only_move = 0;
14131         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14132
14133         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14134         strcat(bookMove, bookHit);
14135         savedMessage = bookMove; // args for deferred call
14136         savedState = onmove;
14137         ScheduleDelayedEvent(DeferredBookMove, 1);
14138     }
14139 }
14140
14141 void
14142 TrainingEvent ()
14143 {
14144     if (gameMode == Training) {
14145       SetTrainingModeOff();
14146       gameMode = PlayFromGameFile;
14147       DisplayMessage("", _("Training mode off"));
14148     } else {
14149       gameMode = Training;
14150       animateTraining = appData.animate;
14151
14152       /* make sure we are not already at the end of the game */
14153       if (currentMove < forwardMostMove) {
14154         SetTrainingModeOn();
14155         DisplayMessage("", _("Training mode on"));
14156       } else {
14157         gameMode = PlayFromGameFile;
14158         DisplayError(_("Already at end of game"), 0);
14159       }
14160     }
14161     ModeHighlight();
14162 }
14163
14164 void
14165 IcsClientEvent ()
14166 {
14167     if (!appData.icsActive) return;
14168     switch (gameMode) {
14169       case IcsPlayingWhite:
14170       case IcsPlayingBlack:
14171       case IcsObserving:
14172       case IcsIdle:
14173       case BeginningOfGame:
14174       case IcsExamining:
14175         return;
14176
14177       case EditGame:
14178         break;
14179
14180       case EditPosition:
14181         EditPositionDone(TRUE);
14182         break;
14183
14184       case AnalyzeMode:
14185       case AnalyzeFile:
14186         ExitAnalyzeMode();
14187         break;
14188
14189       default:
14190         EditGameEvent();
14191         break;
14192     }
14193
14194     gameMode = IcsIdle;
14195     ModeHighlight();
14196     return;
14197 }
14198
14199 void
14200 EditGameEvent ()
14201 {
14202     int i;
14203
14204     switch (gameMode) {
14205       case Training:
14206         SetTrainingModeOff();
14207         break;
14208       case MachinePlaysWhite:
14209       case MachinePlaysBlack:
14210       case BeginningOfGame:
14211         SendToProgram("force\n", &first);
14212         SetUserThinkingEnables();
14213         break;
14214       case PlayFromGameFile:
14215         (void) StopLoadGameTimer();
14216         if (gameFileFP != NULL) {
14217             gameFileFP = NULL;
14218         }
14219         break;
14220       case EditPosition:
14221         EditPositionDone(TRUE);
14222         break;
14223       case AnalyzeMode:
14224       case AnalyzeFile:
14225         ExitAnalyzeMode();
14226         SendToProgram("force\n", &first);
14227         break;
14228       case TwoMachinesPlay:
14229         GameEnds(EndOfFile, NULL, GE_PLAYER);
14230         ResurrectChessProgram();
14231         SetUserThinkingEnables();
14232         break;
14233       case EndOfGame:
14234         ResurrectChessProgram();
14235         break;
14236       case IcsPlayingBlack:
14237       case IcsPlayingWhite:
14238         DisplayError(_("Warning: You are still playing a game"), 0);
14239         break;
14240       case IcsObserving:
14241         DisplayError(_("Warning: You are still observing a game"), 0);
14242         break;
14243       case IcsExamining:
14244         DisplayError(_("Warning: You are still examining a game"), 0);
14245         break;
14246       case IcsIdle:
14247         break;
14248       case EditGame:
14249       default:
14250         return;
14251     }
14252
14253     pausing = FALSE;
14254     StopClocks();
14255     first.offeredDraw = second.offeredDraw = 0;
14256
14257     if (gameMode == PlayFromGameFile) {
14258         whiteTimeRemaining = timeRemaining[0][currentMove];
14259         blackTimeRemaining = timeRemaining[1][currentMove];
14260         DisplayTitle("");
14261     }
14262
14263     if (gameMode == MachinePlaysWhite ||
14264         gameMode == MachinePlaysBlack ||
14265         gameMode == TwoMachinesPlay ||
14266         gameMode == EndOfGame) {
14267         i = forwardMostMove;
14268         while (i > currentMove) {
14269             SendToProgram("undo\n", &first);
14270             i--;
14271         }
14272         if(!adjustedClock) {
14273         whiteTimeRemaining = timeRemaining[0][currentMove];
14274         blackTimeRemaining = timeRemaining[1][currentMove];
14275         DisplayBothClocks();
14276         }
14277         if (whiteFlag || blackFlag) {
14278             whiteFlag = blackFlag = 0;
14279         }
14280         DisplayTitle("");
14281     }
14282
14283     gameMode = EditGame;
14284     ModeHighlight();
14285     SetGameInfo();
14286 }
14287
14288
14289 void
14290 EditPositionEvent ()
14291 {
14292     if (gameMode == EditPosition) {
14293         EditGameEvent();
14294         return;
14295     }
14296
14297     EditGameEvent();
14298     if (gameMode != EditGame) return;
14299
14300     gameMode = EditPosition;
14301     ModeHighlight();
14302     SetGameInfo();
14303     if (currentMove > 0)
14304       CopyBoard(boards[0], boards[currentMove]);
14305
14306     blackPlaysFirst = !WhiteOnMove(currentMove);
14307     ResetClocks();
14308     currentMove = forwardMostMove = backwardMostMove = 0;
14309     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14310     DisplayMove(-1);
14311     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14312 }
14313
14314 void
14315 ExitAnalyzeMode ()
14316 {
14317     /* [DM] icsEngineAnalyze - possible call from other functions */
14318     if (appData.icsEngineAnalyze) {
14319         appData.icsEngineAnalyze = FALSE;
14320
14321         DisplayMessage("",_("Close ICS engine analyze..."));
14322     }
14323     if (first.analysisSupport && first.analyzing) {
14324       SendToBoth("exit\n");
14325       first.analyzing = second.analyzing = FALSE;
14326     }
14327     thinkOutput[0] = NULLCHAR;
14328 }
14329
14330 void
14331 EditPositionDone (Boolean fakeRights)
14332 {
14333     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14334
14335     startedFromSetupPosition = TRUE;
14336     InitChessProgram(&first, FALSE);
14337     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14338       boards[0][EP_STATUS] = EP_NONE;
14339       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14340       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14341         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14342         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14343       } else boards[0][CASTLING][2] = NoRights;
14344       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14345         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14346         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14347       } else boards[0][CASTLING][5] = NoRights;
14348       if(gameInfo.variant == VariantSChess) {
14349         int i;
14350         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14351           boards[0][VIRGIN][i] = 0;
14352           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14353           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14354         }
14355       }
14356     }
14357     SendToProgram("force\n", &first);
14358     if (blackPlaysFirst) {
14359         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14360         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14361         currentMove = forwardMostMove = backwardMostMove = 1;
14362         CopyBoard(boards[1], boards[0]);
14363     } else {
14364         currentMove = forwardMostMove = backwardMostMove = 0;
14365     }
14366     SendBoard(&first, forwardMostMove);
14367     if (appData.debugMode) {
14368         fprintf(debugFP, "EditPosDone\n");
14369     }
14370     DisplayTitle("");
14371     DisplayMessage("", "");
14372     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14373     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14374     gameMode = EditGame;
14375     ModeHighlight();
14376     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14377     ClearHighlights(); /* [AS] */
14378 }
14379
14380 /* Pause for `ms' milliseconds */
14381 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14382 void
14383 TimeDelay (long ms)
14384 {
14385     TimeMark m1, m2;
14386
14387     GetTimeMark(&m1);
14388     do {
14389         GetTimeMark(&m2);
14390     } while (SubtractTimeMarks(&m2, &m1) < ms);
14391 }
14392
14393 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14394 void
14395 SendMultiLineToICS (char *buf)
14396 {
14397     char temp[MSG_SIZ+1], *p;
14398     int len;
14399
14400     len = strlen(buf);
14401     if (len > MSG_SIZ)
14402       len = MSG_SIZ;
14403
14404     strncpy(temp, buf, len);
14405     temp[len] = 0;
14406
14407     p = temp;
14408     while (*p) {
14409         if (*p == '\n' || *p == '\r')
14410           *p = ' ';
14411         ++p;
14412     }
14413
14414     strcat(temp, "\n");
14415     SendToICS(temp);
14416     SendToPlayer(temp, strlen(temp));
14417 }
14418
14419 void
14420 SetWhiteToPlayEvent ()
14421 {
14422     if (gameMode == EditPosition) {
14423         blackPlaysFirst = FALSE;
14424         DisplayBothClocks();    /* works because currentMove is 0 */
14425     } else if (gameMode == IcsExamining) {
14426         SendToICS(ics_prefix);
14427         SendToICS("tomove white\n");
14428     }
14429 }
14430
14431 void
14432 SetBlackToPlayEvent ()
14433 {
14434     if (gameMode == EditPosition) {
14435         blackPlaysFirst = TRUE;
14436         currentMove = 1;        /* kludge */
14437         DisplayBothClocks();
14438         currentMove = 0;
14439     } else if (gameMode == IcsExamining) {
14440         SendToICS(ics_prefix);
14441         SendToICS("tomove black\n");
14442     }
14443 }
14444
14445 void
14446 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14447 {
14448     char buf[MSG_SIZ];
14449     ChessSquare piece = boards[0][y][x];
14450
14451     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14452
14453     switch (selection) {
14454       case ClearBoard:
14455         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14456             SendToICS(ics_prefix);
14457             SendToICS("bsetup clear\n");
14458         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14459             SendToICS(ics_prefix);
14460             SendToICS("clearboard\n");
14461         } else {
14462             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14463                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14464                 for (y = 0; y < BOARD_HEIGHT; y++) {
14465                     if (gameMode == IcsExamining) {
14466                         if (boards[currentMove][y][x] != EmptySquare) {
14467                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14468                                     AAA + x, ONE + y);
14469                             SendToICS(buf);
14470                         }
14471                     } else {
14472                         boards[0][y][x] = p;
14473                     }
14474                 }
14475             }
14476         }
14477         if (gameMode == EditPosition) {
14478             DrawPosition(FALSE, boards[0]);
14479         }
14480         break;
14481
14482       case WhitePlay:
14483         SetWhiteToPlayEvent();
14484         break;
14485
14486       case BlackPlay:
14487         SetBlackToPlayEvent();
14488         break;
14489
14490       case EmptySquare:
14491         if (gameMode == IcsExamining) {
14492             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14493             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14494             SendToICS(buf);
14495         } else {
14496             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14497                 if(x == BOARD_LEFT-2) {
14498                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14499                     boards[0][y][1] = 0;
14500                 } else
14501                 if(x == BOARD_RGHT+1) {
14502                     if(y >= gameInfo.holdingsSize) break;
14503                     boards[0][y][BOARD_WIDTH-2] = 0;
14504                 } else break;
14505             }
14506             boards[0][y][x] = EmptySquare;
14507             DrawPosition(FALSE, boards[0]);
14508         }
14509         break;
14510
14511       case PromotePiece:
14512         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14513            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14514             selection = (ChessSquare) (PROMOTED piece);
14515         } else if(piece == EmptySquare) selection = WhiteSilver;
14516         else selection = (ChessSquare)((int)piece - 1);
14517         goto defaultlabel;
14518
14519       case DemotePiece:
14520         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14521            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14522             selection = (ChessSquare) (DEMOTED piece);
14523         } else if(piece == EmptySquare) selection = BlackSilver;
14524         else selection = (ChessSquare)((int)piece + 1);
14525         goto defaultlabel;
14526
14527       case WhiteQueen:
14528       case BlackQueen:
14529         if(gameInfo.variant == VariantShatranj ||
14530            gameInfo.variant == VariantXiangqi  ||
14531            gameInfo.variant == VariantCourier  ||
14532            gameInfo.variant == VariantMakruk     )
14533             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14534         goto defaultlabel;
14535
14536       case WhiteKing:
14537       case BlackKing:
14538         if(gameInfo.variant == VariantXiangqi)
14539             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14540         if(gameInfo.variant == VariantKnightmate)
14541             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14542       default:
14543         defaultlabel:
14544         if (gameMode == IcsExamining) {
14545             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14546             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14547                      PieceToChar(selection), AAA + x, ONE + y);
14548             SendToICS(buf);
14549         } else {
14550             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14551                 int n;
14552                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14553                     n = PieceToNumber(selection - BlackPawn);
14554                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14555                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14556                     boards[0][BOARD_HEIGHT-1-n][1]++;
14557                 } else
14558                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14559                     n = PieceToNumber(selection);
14560                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14561                     boards[0][n][BOARD_WIDTH-1] = selection;
14562                     boards[0][n][BOARD_WIDTH-2]++;
14563                 }
14564             } else
14565             boards[0][y][x] = selection;
14566             DrawPosition(TRUE, boards[0]);
14567             ClearHighlights();
14568             fromX = fromY = -1;
14569         }
14570         break;
14571     }
14572 }
14573
14574
14575 void
14576 DropMenuEvent (ChessSquare selection, int x, int y)
14577 {
14578     ChessMove moveType;
14579
14580     switch (gameMode) {
14581       case IcsPlayingWhite:
14582       case MachinePlaysBlack:
14583         if (!WhiteOnMove(currentMove)) {
14584             DisplayMoveError(_("It is Black's turn"));
14585             return;
14586         }
14587         moveType = WhiteDrop;
14588         break;
14589       case IcsPlayingBlack:
14590       case MachinePlaysWhite:
14591         if (WhiteOnMove(currentMove)) {
14592             DisplayMoveError(_("It is White's turn"));
14593             return;
14594         }
14595         moveType = BlackDrop;
14596         break;
14597       case EditGame:
14598         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14599         break;
14600       default:
14601         return;
14602     }
14603
14604     if (moveType == BlackDrop && selection < BlackPawn) {
14605       selection = (ChessSquare) ((int) selection
14606                                  + (int) BlackPawn - (int) WhitePawn);
14607     }
14608     if (boards[currentMove][y][x] != EmptySquare) {
14609         DisplayMoveError(_("That square is occupied"));
14610         return;
14611     }
14612
14613     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14614 }
14615
14616 void
14617 AcceptEvent ()
14618 {
14619     /* Accept a pending offer of any kind from opponent */
14620
14621     if (appData.icsActive) {
14622         SendToICS(ics_prefix);
14623         SendToICS("accept\n");
14624     } else if (cmailMsgLoaded) {
14625         if (currentMove == cmailOldMove &&
14626             commentList[cmailOldMove] != NULL &&
14627             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14628                    "Black offers a draw" : "White offers a draw")) {
14629             TruncateGame();
14630             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14631             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14632         } else {
14633             DisplayError(_("There is no pending offer on this move"), 0);
14634             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14635         }
14636     } else {
14637         /* Not used for offers from chess program */
14638     }
14639 }
14640
14641 void
14642 DeclineEvent ()
14643 {
14644     /* Decline a pending offer of any kind from opponent */
14645
14646     if (appData.icsActive) {
14647         SendToICS(ics_prefix);
14648         SendToICS("decline\n");
14649     } else if (cmailMsgLoaded) {
14650         if (currentMove == cmailOldMove &&
14651             commentList[cmailOldMove] != NULL &&
14652             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14653                    "Black offers a draw" : "White offers a draw")) {
14654 #ifdef NOTDEF
14655             AppendComment(cmailOldMove, "Draw declined", TRUE);
14656             DisplayComment(cmailOldMove - 1, "Draw declined");
14657 #endif /*NOTDEF*/
14658         } else {
14659             DisplayError(_("There is no pending offer on this move"), 0);
14660         }
14661     } else {
14662         /* Not used for offers from chess program */
14663     }
14664 }
14665
14666 void
14667 RematchEvent ()
14668 {
14669     /* Issue ICS rematch command */
14670     if (appData.icsActive) {
14671         SendToICS(ics_prefix);
14672         SendToICS("rematch\n");
14673     }
14674 }
14675
14676 void
14677 CallFlagEvent ()
14678 {
14679     /* Call your opponent's flag (claim a win on time) */
14680     if (appData.icsActive) {
14681         SendToICS(ics_prefix);
14682         SendToICS("flag\n");
14683     } else {
14684         switch (gameMode) {
14685           default:
14686             return;
14687           case MachinePlaysWhite:
14688             if (whiteFlag) {
14689                 if (blackFlag)
14690                   GameEnds(GameIsDrawn, "Both players ran out of time",
14691                            GE_PLAYER);
14692                 else
14693                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14694             } else {
14695                 DisplayError(_("Your opponent is not out of time"), 0);
14696             }
14697             break;
14698           case MachinePlaysBlack:
14699             if (blackFlag) {
14700                 if (whiteFlag)
14701                   GameEnds(GameIsDrawn, "Both players ran out of time",
14702                            GE_PLAYER);
14703                 else
14704                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14705             } else {
14706                 DisplayError(_("Your opponent is not out of time"), 0);
14707             }
14708             break;
14709         }
14710     }
14711 }
14712
14713 void
14714 ClockClick (int which)
14715 {       // [HGM] code moved to back-end from winboard.c
14716         if(which) { // black clock
14717           if (gameMode == EditPosition || gameMode == IcsExamining) {
14718             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14719             SetBlackToPlayEvent();
14720           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14721           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14722           } else if (shiftKey) {
14723             AdjustClock(which, -1);
14724           } else if (gameMode == IcsPlayingWhite ||
14725                      gameMode == MachinePlaysBlack) {
14726             CallFlagEvent();
14727           }
14728         } else { // white clock
14729           if (gameMode == EditPosition || gameMode == IcsExamining) {
14730             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14731             SetWhiteToPlayEvent();
14732           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14733           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14734           } else if (shiftKey) {
14735             AdjustClock(which, -1);
14736           } else if (gameMode == IcsPlayingBlack ||
14737                    gameMode == MachinePlaysWhite) {
14738             CallFlagEvent();
14739           }
14740         }
14741 }
14742
14743 void
14744 DrawEvent ()
14745 {
14746     /* Offer draw or accept pending draw offer from opponent */
14747
14748     if (appData.icsActive) {
14749         /* Note: tournament rules require draw offers to be
14750            made after you make your move but before you punch
14751            your clock.  Currently ICS doesn't let you do that;
14752            instead, you immediately punch your clock after making
14753            a move, but you can offer a draw at any time. */
14754
14755         SendToICS(ics_prefix);
14756         SendToICS("draw\n");
14757         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14758     } else if (cmailMsgLoaded) {
14759         if (currentMove == cmailOldMove &&
14760             commentList[cmailOldMove] != NULL &&
14761             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14762                    "Black offers a draw" : "White offers a draw")) {
14763             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14764             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14765         } else if (currentMove == cmailOldMove + 1) {
14766             char *offer = WhiteOnMove(cmailOldMove) ?
14767               "White offers a draw" : "Black offers a draw";
14768             AppendComment(currentMove, offer, TRUE);
14769             DisplayComment(currentMove - 1, offer);
14770             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14771         } else {
14772             DisplayError(_("You must make your move before offering a draw"), 0);
14773             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14774         }
14775     } else if (first.offeredDraw) {
14776         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14777     } else {
14778         if (first.sendDrawOffers) {
14779             SendToProgram("draw\n", &first);
14780             userOfferedDraw = TRUE;
14781         }
14782     }
14783 }
14784
14785 void
14786 AdjournEvent ()
14787 {
14788     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14789
14790     if (appData.icsActive) {
14791         SendToICS(ics_prefix);
14792         SendToICS("adjourn\n");
14793     } else {
14794         /* Currently GNU Chess doesn't offer or accept Adjourns */
14795     }
14796 }
14797
14798
14799 void
14800 AbortEvent ()
14801 {
14802     /* Offer Abort or accept pending Abort offer from opponent */
14803
14804     if (appData.icsActive) {
14805         SendToICS(ics_prefix);
14806         SendToICS("abort\n");
14807     } else {
14808         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14809     }
14810 }
14811
14812 void
14813 ResignEvent ()
14814 {
14815     /* Resign.  You can do this even if it's not your turn. */
14816
14817     if (appData.icsActive) {
14818         SendToICS(ics_prefix);
14819         SendToICS("resign\n");
14820     } else {
14821         switch (gameMode) {
14822           case MachinePlaysWhite:
14823             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14824             break;
14825           case MachinePlaysBlack:
14826             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14827             break;
14828           case EditGame:
14829             if (cmailMsgLoaded) {
14830                 TruncateGame();
14831                 if (WhiteOnMove(cmailOldMove)) {
14832                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14833                 } else {
14834                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14835                 }
14836                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14837             }
14838             break;
14839           default:
14840             break;
14841         }
14842     }
14843 }
14844
14845
14846 void
14847 StopObservingEvent ()
14848 {
14849     /* Stop observing current games */
14850     SendToICS(ics_prefix);
14851     SendToICS("unobserve\n");
14852 }
14853
14854 void
14855 StopExaminingEvent ()
14856 {
14857     /* Stop observing current game */
14858     SendToICS(ics_prefix);
14859     SendToICS("unexamine\n");
14860 }
14861
14862 void
14863 ForwardInner (int target)
14864 {
14865     int limit; int oldSeekGraphUp = seekGraphUp;
14866
14867     if (appData.debugMode)
14868         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14869                 target, currentMove, forwardMostMove);
14870
14871     if (gameMode == EditPosition)
14872       return;
14873
14874     seekGraphUp = FALSE;
14875     MarkTargetSquares(1);
14876
14877     if (gameMode == PlayFromGameFile && !pausing)
14878       PauseEvent();
14879
14880     if (gameMode == IcsExamining && pausing)
14881       limit = pauseExamForwardMostMove;
14882     else
14883       limit = forwardMostMove;
14884
14885     if (target > limit) target = limit;
14886
14887     if (target > 0 && moveList[target - 1][0]) {
14888         int fromX, fromY, toX, toY;
14889         toX = moveList[target - 1][2] - AAA;
14890         toY = moveList[target - 1][3] - ONE;
14891         if (moveList[target - 1][1] == '@') {
14892             if (appData.highlightLastMove) {
14893                 SetHighlights(-1, -1, toX, toY);
14894             }
14895         } else {
14896             fromX = moveList[target - 1][0] - AAA;
14897             fromY = moveList[target - 1][1] - ONE;
14898             if (target == currentMove + 1) {
14899                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14900             }
14901             if (appData.highlightLastMove) {
14902                 SetHighlights(fromX, fromY, toX, toY);
14903             }
14904         }
14905     }
14906     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14907         gameMode == Training || gameMode == PlayFromGameFile ||
14908         gameMode == AnalyzeFile) {
14909         while (currentMove < target) {
14910             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14911             SendMoveToProgram(currentMove++, &first);
14912         }
14913     } else {
14914         currentMove = target;
14915     }
14916
14917     if (gameMode == EditGame || gameMode == EndOfGame) {
14918         whiteTimeRemaining = timeRemaining[0][currentMove];
14919         blackTimeRemaining = timeRemaining[1][currentMove];
14920     }
14921     DisplayBothClocks();
14922     DisplayMove(currentMove - 1);
14923     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14924     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14925     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14926         DisplayComment(currentMove - 1, commentList[currentMove]);
14927     }
14928     ClearMap(); // [HGM] exclude: invalidate map
14929 }
14930
14931
14932 void
14933 ForwardEvent ()
14934 {
14935     if (gameMode == IcsExamining && !pausing) {
14936         SendToICS(ics_prefix);
14937         SendToICS("forward\n");
14938     } else {
14939         ForwardInner(currentMove + 1);
14940     }
14941 }
14942
14943 void
14944 ToEndEvent ()
14945 {
14946     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14947         /* to optimze, we temporarily turn off analysis mode while we feed
14948          * the remaining moves to the engine. Otherwise we get analysis output
14949          * after each move.
14950          */
14951         if (first.analysisSupport) {
14952           SendToProgram("exit\nforce\n", &first);
14953           first.analyzing = FALSE;
14954         }
14955     }
14956
14957     if (gameMode == IcsExamining && !pausing) {
14958         SendToICS(ics_prefix);
14959         SendToICS("forward 999999\n");
14960     } else {
14961         ForwardInner(forwardMostMove);
14962     }
14963
14964     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14965         /* we have fed all the moves, so reactivate analysis mode */
14966         SendToProgram("analyze\n", &first);
14967         first.analyzing = TRUE;
14968         /*first.maybeThinking = TRUE;*/
14969         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14970     }
14971 }
14972
14973 void
14974 BackwardInner (int target)
14975 {
14976     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14977
14978     if (appData.debugMode)
14979         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14980                 target, currentMove, forwardMostMove);
14981
14982     if (gameMode == EditPosition) return;
14983     seekGraphUp = FALSE;
14984     MarkTargetSquares(1);
14985     if (currentMove <= backwardMostMove) {
14986         ClearHighlights();
14987         DrawPosition(full_redraw, boards[currentMove]);
14988         return;
14989     }
14990     if (gameMode == PlayFromGameFile && !pausing)
14991       PauseEvent();
14992
14993     if (moveList[target][0]) {
14994         int fromX, fromY, toX, toY;
14995         toX = moveList[target][2] - AAA;
14996         toY = moveList[target][3] - ONE;
14997         if (moveList[target][1] == '@') {
14998             if (appData.highlightLastMove) {
14999                 SetHighlights(-1, -1, toX, toY);
15000             }
15001         } else {
15002             fromX = moveList[target][0] - AAA;
15003             fromY = moveList[target][1] - ONE;
15004             if (target == currentMove - 1) {
15005                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15006             }
15007             if (appData.highlightLastMove) {
15008                 SetHighlights(fromX, fromY, toX, toY);
15009             }
15010         }
15011     }
15012     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15013         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15014         while (currentMove > target) {
15015             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15016                 // null move cannot be undone. Reload program with move history before it.
15017                 int i;
15018                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15019                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15020                 }
15021                 SendBoard(&first, i);
15022               if(second.analyzing) SendBoard(&second, i);
15023                 for(currentMove=i; currentMove<target; currentMove++) {
15024                     SendMoveToProgram(currentMove, &first);
15025                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15026                 }
15027                 break;
15028             }
15029             SendToBoth("undo\n");
15030             currentMove--;
15031         }
15032     } else {
15033         currentMove = target;
15034     }
15035
15036     if (gameMode == EditGame || gameMode == EndOfGame) {
15037         whiteTimeRemaining = timeRemaining[0][currentMove];
15038         blackTimeRemaining = timeRemaining[1][currentMove];
15039     }
15040     DisplayBothClocks();
15041     DisplayMove(currentMove - 1);
15042     DrawPosition(full_redraw, boards[currentMove]);
15043     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15044     // [HGM] PV info: routine tests if comment empty
15045     DisplayComment(currentMove - 1, commentList[currentMove]);
15046     ClearMap(); // [HGM] exclude: invalidate map
15047 }
15048
15049 void
15050 BackwardEvent ()
15051 {
15052     if (gameMode == IcsExamining && !pausing) {
15053         SendToICS(ics_prefix);
15054         SendToICS("backward\n");
15055     } else {
15056         BackwardInner(currentMove - 1);
15057     }
15058 }
15059
15060 void
15061 ToStartEvent ()
15062 {
15063     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15064         /* to optimize, we temporarily turn off analysis mode while we undo
15065          * all the moves. Otherwise we get analysis output after each undo.
15066          */
15067         if (first.analysisSupport) {
15068           SendToProgram("exit\nforce\n", &first);
15069           first.analyzing = FALSE;
15070         }
15071     }
15072
15073     if (gameMode == IcsExamining && !pausing) {
15074         SendToICS(ics_prefix);
15075         SendToICS("backward 999999\n");
15076     } else {
15077         BackwardInner(backwardMostMove);
15078     }
15079
15080     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15081         /* we have fed all the moves, so reactivate analysis mode */
15082         SendToProgram("analyze\n", &first);
15083         first.analyzing = TRUE;
15084         /*first.maybeThinking = TRUE;*/
15085         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15086     }
15087 }
15088
15089 void
15090 ToNrEvent (int to)
15091 {
15092   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15093   if (to >= forwardMostMove) to = forwardMostMove;
15094   if (to <= backwardMostMove) to = backwardMostMove;
15095   if (to < currentMove) {
15096     BackwardInner(to);
15097   } else {
15098     ForwardInner(to);
15099   }
15100 }
15101
15102 void
15103 RevertEvent (Boolean annotate)
15104 {
15105     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15106         return;
15107     }
15108     if (gameMode != IcsExamining) {
15109         DisplayError(_("You are not examining a game"), 0);
15110         return;
15111     }
15112     if (pausing) {
15113         DisplayError(_("You can't revert while pausing"), 0);
15114         return;
15115     }
15116     SendToICS(ics_prefix);
15117     SendToICS("revert\n");
15118 }
15119
15120 void
15121 RetractMoveEvent ()
15122 {
15123     switch (gameMode) {
15124       case MachinePlaysWhite:
15125       case MachinePlaysBlack:
15126         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15127             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15128             return;
15129         }
15130         if (forwardMostMove < 2) return;
15131         currentMove = forwardMostMove = forwardMostMove - 2;
15132         whiteTimeRemaining = timeRemaining[0][currentMove];
15133         blackTimeRemaining = timeRemaining[1][currentMove];
15134         DisplayBothClocks();
15135         DisplayMove(currentMove - 1);
15136         ClearHighlights();/*!! could figure this out*/
15137         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15138         SendToProgram("remove\n", &first);
15139         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15140         break;
15141
15142       case BeginningOfGame:
15143       default:
15144         break;
15145
15146       case IcsPlayingWhite:
15147       case IcsPlayingBlack:
15148         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15149             SendToICS(ics_prefix);
15150             SendToICS("takeback 2\n");
15151         } else {
15152             SendToICS(ics_prefix);
15153             SendToICS("takeback 1\n");
15154         }
15155         break;
15156     }
15157 }
15158
15159 void
15160 MoveNowEvent ()
15161 {
15162     ChessProgramState *cps;
15163
15164     switch (gameMode) {
15165       case MachinePlaysWhite:
15166         if (!WhiteOnMove(forwardMostMove)) {
15167             DisplayError(_("It is your turn"), 0);
15168             return;
15169         }
15170         cps = &first;
15171         break;
15172       case MachinePlaysBlack:
15173         if (WhiteOnMove(forwardMostMove)) {
15174             DisplayError(_("It is your turn"), 0);
15175             return;
15176         }
15177         cps = &first;
15178         break;
15179       case TwoMachinesPlay:
15180         if (WhiteOnMove(forwardMostMove) ==
15181             (first.twoMachinesColor[0] == 'w')) {
15182             cps = &first;
15183         } else {
15184             cps = &second;
15185         }
15186         break;
15187       case BeginningOfGame:
15188       default:
15189         return;
15190     }
15191     SendToProgram("?\n", cps);
15192 }
15193
15194 void
15195 TruncateGameEvent ()
15196 {
15197     EditGameEvent();
15198     if (gameMode != EditGame) return;
15199     TruncateGame();
15200 }
15201
15202 void
15203 TruncateGame ()
15204 {
15205     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15206     if (forwardMostMove > currentMove) {
15207         if (gameInfo.resultDetails != NULL) {
15208             free(gameInfo.resultDetails);
15209             gameInfo.resultDetails = NULL;
15210             gameInfo.result = GameUnfinished;
15211         }
15212         forwardMostMove = currentMove;
15213         HistorySet(parseList, backwardMostMove, forwardMostMove,
15214                    currentMove-1);
15215     }
15216 }
15217
15218 void
15219 HintEvent ()
15220 {
15221     if (appData.noChessProgram) return;
15222     switch (gameMode) {
15223       case MachinePlaysWhite:
15224         if (WhiteOnMove(forwardMostMove)) {
15225             DisplayError(_("Wait until your turn"), 0);
15226             return;
15227         }
15228         break;
15229       case BeginningOfGame:
15230       case MachinePlaysBlack:
15231         if (!WhiteOnMove(forwardMostMove)) {
15232             DisplayError(_("Wait until your turn"), 0);
15233             return;
15234         }
15235         break;
15236       default:
15237         DisplayError(_("No hint available"), 0);
15238         return;
15239     }
15240     SendToProgram("hint\n", &first);
15241     hintRequested = TRUE;
15242 }
15243
15244 void
15245 CreateBookEvent ()
15246 {
15247     ListGame * lg = (ListGame *) gameList.head;
15248     FILE *f, *g;
15249     int nItem;
15250     static int secondTime = FALSE;
15251
15252     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15253         DisplayError(_("Game list not loaded or empty"), 0);
15254         return;
15255     }
15256
15257     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15258         fclose(g);
15259         secondTime++;
15260         DisplayNote(_("Book file exists! Try again for overwrite."));
15261         return;
15262     }
15263
15264     creatingBook = TRUE;
15265     secondTime = FALSE;
15266
15267     /* Get list size */
15268     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15269         LoadGame(f, nItem, "", TRUE);
15270         AddGameToBook(TRUE);
15271         lg = (ListGame *) lg->node.succ;
15272     }
15273
15274     creatingBook = FALSE;
15275     FlushBook();
15276 }
15277
15278 void
15279 BookEvent ()
15280 {
15281     if (appData.noChessProgram) return;
15282     switch (gameMode) {
15283       case MachinePlaysWhite:
15284         if (WhiteOnMove(forwardMostMove)) {
15285             DisplayError(_("Wait until your turn"), 0);
15286             return;
15287         }
15288         break;
15289       case BeginningOfGame:
15290       case MachinePlaysBlack:
15291         if (!WhiteOnMove(forwardMostMove)) {
15292             DisplayError(_("Wait until your turn"), 0);
15293             return;
15294         }
15295         break;
15296       case EditPosition:
15297         EditPositionDone(TRUE);
15298         break;
15299       case TwoMachinesPlay:
15300         return;
15301       default:
15302         break;
15303     }
15304     SendToProgram("bk\n", &first);
15305     bookOutput[0] = NULLCHAR;
15306     bookRequested = TRUE;
15307 }
15308
15309 void
15310 AboutGameEvent ()
15311 {
15312     char *tags = PGNTags(&gameInfo);
15313     TagsPopUp(tags, CmailMsg());
15314     free(tags);
15315 }
15316
15317 /* end button procedures */
15318
15319 void
15320 PrintPosition (FILE *fp, int move)
15321 {
15322     int i, j;
15323
15324     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15325         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15326             char c = PieceToChar(boards[move][i][j]);
15327             fputc(c == 'x' ? '.' : c, fp);
15328             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15329         }
15330     }
15331     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15332       fprintf(fp, "white to play\n");
15333     else
15334       fprintf(fp, "black to play\n");
15335 }
15336
15337 void
15338 PrintOpponents (FILE *fp)
15339 {
15340     if (gameInfo.white != NULL) {
15341         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15342     } else {
15343         fprintf(fp, "\n");
15344     }
15345 }
15346
15347 /* Find last component of program's own name, using some heuristics */
15348 void
15349 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15350 {
15351     char *p, *q, c;
15352     int local = (strcmp(host, "localhost") == 0);
15353     while (!local && (p = strchr(prog, ';')) != NULL) {
15354         p++;
15355         while (*p == ' ') p++;
15356         prog = p;
15357     }
15358     if (*prog == '"' || *prog == '\'') {
15359         q = strchr(prog + 1, *prog);
15360     } else {
15361         q = strchr(prog, ' ');
15362     }
15363     if (q == NULL) q = prog + strlen(prog);
15364     p = q;
15365     while (p >= prog && *p != '/' && *p != '\\') p--;
15366     p++;
15367     if(p == prog && *p == '"') p++;
15368     c = *q; *q = 0;
15369     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15370     memcpy(buf, p, q - p);
15371     buf[q - p] = NULLCHAR;
15372     if (!local) {
15373         strcat(buf, "@");
15374         strcat(buf, host);
15375     }
15376 }
15377
15378 char *
15379 TimeControlTagValue ()
15380 {
15381     char buf[MSG_SIZ];
15382     if (!appData.clockMode) {
15383       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15384     } else if (movesPerSession > 0) {
15385       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15386     } else if (timeIncrement == 0) {
15387       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15388     } else {
15389       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15390     }
15391     return StrSave(buf);
15392 }
15393
15394 void
15395 SetGameInfo ()
15396 {
15397     /* This routine is used only for certain modes */
15398     VariantClass v = gameInfo.variant;
15399     ChessMove r = GameUnfinished;
15400     char *p = NULL;
15401
15402     if(keepInfo) return;
15403
15404     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15405         r = gameInfo.result;
15406         p = gameInfo.resultDetails;
15407         gameInfo.resultDetails = NULL;
15408     }
15409     ClearGameInfo(&gameInfo);
15410     gameInfo.variant = v;
15411
15412     switch (gameMode) {
15413       case MachinePlaysWhite:
15414         gameInfo.event = StrSave( appData.pgnEventHeader );
15415         gameInfo.site = StrSave(HostName());
15416         gameInfo.date = PGNDate();
15417         gameInfo.round = StrSave("-");
15418         gameInfo.white = StrSave(first.tidy);
15419         gameInfo.black = StrSave(UserName());
15420         gameInfo.timeControl = TimeControlTagValue();
15421         break;
15422
15423       case MachinePlaysBlack:
15424         gameInfo.event = StrSave( appData.pgnEventHeader );
15425         gameInfo.site = StrSave(HostName());
15426         gameInfo.date = PGNDate();
15427         gameInfo.round = StrSave("-");
15428         gameInfo.white = StrSave(UserName());
15429         gameInfo.black = StrSave(first.tidy);
15430         gameInfo.timeControl = TimeControlTagValue();
15431         break;
15432
15433       case TwoMachinesPlay:
15434         gameInfo.event = StrSave( appData.pgnEventHeader );
15435         gameInfo.site = StrSave(HostName());
15436         gameInfo.date = PGNDate();
15437         if (roundNr > 0) {
15438             char buf[MSG_SIZ];
15439             snprintf(buf, MSG_SIZ, "%d", roundNr);
15440             gameInfo.round = StrSave(buf);
15441         } else {
15442             gameInfo.round = StrSave("-");
15443         }
15444         if (first.twoMachinesColor[0] == 'w') {
15445             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15446             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15447         } else {
15448             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15449             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15450         }
15451         gameInfo.timeControl = TimeControlTagValue();
15452         break;
15453
15454       case EditGame:
15455         gameInfo.event = StrSave("Edited game");
15456         gameInfo.site = StrSave(HostName());
15457         gameInfo.date = PGNDate();
15458         gameInfo.round = StrSave("-");
15459         gameInfo.white = StrSave("-");
15460         gameInfo.black = StrSave("-");
15461         gameInfo.result = r;
15462         gameInfo.resultDetails = p;
15463         break;
15464
15465       case EditPosition:
15466         gameInfo.event = StrSave("Edited position");
15467         gameInfo.site = StrSave(HostName());
15468         gameInfo.date = PGNDate();
15469         gameInfo.round = StrSave("-");
15470         gameInfo.white = StrSave("-");
15471         gameInfo.black = StrSave("-");
15472         break;
15473
15474       case IcsPlayingWhite:
15475       case IcsPlayingBlack:
15476       case IcsObserving:
15477       case IcsExamining:
15478         break;
15479
15480       case PlayFromGameFile:
15481         gameInfo.event = StrSave("Game from non-PGN file");
15482         gameInfo.site = StrSave(HostName());
15483         gameInfo.date = PGNDate();
15484         gameInfo.round = StrSave("-");
15485         gameInfo.white = StrSave("?");
15486         gameInfo.black = StrSave("?");
15487         break;
15488
15489       default:
15490         break;
15491     }
15492 }
15493
15494 void
15495 ReplaceComment (int index, char *text)
15496 {
15497     int len;
15498     char *p;
15499     float score;
15500
15501     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15502        pvInfoList[index-1].depth == len &&
15503        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15504        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15505     while (*text == '\n') text++;
15506     len = strlen(text);
15507     while (len > 0 && text[len - 1] == '\n') len--;
15508
15509     if (commentList[index] != NULL)
15510       free(commentList[index]);
15511
15512     if (len == 0) {
15513         commentList[index] = NULL;
15514         return;
15515     }
15516   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15517       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15518       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15519     commentList[index] = (char *) malloc(len + 2);
15520     strncpy(commentList[index], text, len);
15521     commentList[index][len] = '\n';
15522     commentList[index][len + 1] = NULLCHAR;
15523   } else {
15524     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15525     char *p;
15526     commentList[index] = (char *) malloc(len + 7);
15527     safeStrCpy(commentList[index], "{\n", 3);
15528     safeStrCpy(commentList[index]+2, text, len+1);
15529     commentList[index][len+2] = NULLCHAR;
15530     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15531     strcat(commentList[index], "\n}\n");
15532   }
15533 }
15534
15535 void
15536 CrushCRs (char *text)
15537 {
15538   char *p = text;
15539   char *q = text;
15540   char ch;
15541
15542   do {
15543     ch = *p++;
15544     if (ch == '\r') continue;
15545     *q++ = ch;
15546   } while (ch != '\0');
15547 }
15548
15549 void
15550 AppendComment (int index, char *text, Boolean addBraces)
15551 /* addBraces  tells if we should add {} */
15552 {
15553     int oldlen, len;
15554     char *old;
15555
15556 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15557     if(addBraces == 3) addBraces = 0; else // force appending literally
15558     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15559
15560     CrushCRs(text);
15561     while (*text == '\n') text++;
15562     len = strlen(text);
15563     while (len > 0 && text[len - 1] == '\n') len--;
15564     text[len] = NULLCHAR;
15565
15566     if (len == 0) return;
15567
15568     if (commentList[index] != NULL) {
15569       Boolean addClosingBrace = addBraces;
15570         old = commentList[index];
15571         oldlen = strlen(old);
15572         while(commentList[index][oldlen-1] ==  '\n')
15573           commentList[index][--oldlen] = NULLCHAR;
15574         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15575         safeStrCpy(commentList[index], old, oldlen + len + 6);
15576         free(old);
15577         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15578         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15579           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15580           while (*text == '\n') { text++; len--; }
15581           commentList[index][--oldlen] = NULLCHAR;
15582       }
15583         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15584         else          strcat(commentList[index], "\n");
15585         strcat(commentList[index], text);
15586         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15587         else          strcat(commentList[index], "\n");
15588     } else {
15589         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15590         if(addBraces)
15591           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15592         else commentList[index][0] = NULLCHAR;
15593         strcat(commentList[index], text);
15594         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15595         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15596     }
15597 }
15598
15599 static char *
15600 FindStr (char * text, char * sub_text)
15601 {
15602     char * result = strstr( text, sub_text );
15603
15604     if( result != NULL ) {
15605         result += strlen( sub_text );
15606     }
15607
15608     return result;
15609 }
15610
15611 /* [AS] Try to extract PV info from PGN comment */
15612 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15613 char *
15614 GetInfoFromComment (int index, char * text)
15615 {
15616     char * sep = text, *p;
15617
15618     if( text != NULL && index > 0 ) {
15619         int score = 0;
15620         int depth = 0;
15621         int time = -1, sec = 0, deci;
15622         char * s_eval = FindStr( text, "[%eval " );
15623         char * s_emt = FindStr( text, "[%emt " );
15624 #if 0
15625         if( s_eval != NULL || s_emt != NULL ) {
15626 #else
15627         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15628 #endif
15629             /* New style */
15630             char delim;
15631
15632             if( s_eval != NULL ) {
15633                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15634                     return text;
15635                 }
15636
15637                 if( delim != ']' ) {
15638                     return text;
15639                 }
15640             }
15641
15642             if( s_emt != NULL ) {
15643             }
15644                 return text;
15645         }
15646         else {
15647             /* We expect something like: [+|-]nnn.nn/dd */
15648             int score_lo = 0;
15649
15650             if(*text != '{') return text; // [HGM] braces: must be normal comment
15651
15652             sep = strchr( text, '/' );
15653             if( sep == NULL || sep < (text+4) ) {
15654                 return text;
15655             }
15656
15657             p = text;
15658             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15659             if(p[1] == '(') { // comment starts with PV
15660                p = strchr(p, ')'); // locate end of PV
15661                if(p == NULL || sep < p+5) return text;
15662                // at this point we have something like "{(.*) +0.23/6 ..."
15663                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15664                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15665                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15666             }
15667             time = -1; sec = -1; deci = -1;
15668             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15669                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15670                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15671                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15672                 return text;
15673             }
15674
15675             if( score_lo < 0 || score_lo >= 100 ) {
15676                 return text;
15677             }
15678
15679             if(sec >= 0) time = 600*time + 10*sec; else
15680             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15681
15682             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15683
15684             /* [HGM] PV time: now locate end of PV info */
15685             while( *++sep >= '0' && *sep <= '9'); // strip depth
15686             if(time >= 0)
15687             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15688             if(sec >= 0)
15689             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15690             if(deci >= 0)
15691             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15692             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15693         }
15694
15695         if( depth <= 0 ) {
15696             return text;
15697         }
15698
15699         if( time < 0 ) {
15700             time = -1;
15701         }
15702
15703         pvInfoList[index-1].depth = depth;
15704         pvInfoList[index-1].score = score;
15705         pvInfoList[index-1].time  = 10*time; // centi-sec
15706         if(*sep == '}') *sep = 0; else *--sep = '{';
15707         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15708     }
15709     return sep;
15710 }
15711
15712 void
15713 SendToProgram (char *message, ChessProgramState *cps)
15714 {
15715     int count, outCount, error;
15716     char buf[MSG_SIZ];
15717
15718     if (cps->pr == NoProc) return;
15719     Attention(cps);
15720
15721     if (appData.debugMode) {
15722         TimeMark now;
15723         GetTimeMark(&now);
15724         fprintf(debugFP, "%ld >%-6s: %s",
15725                 SubtractTimeMarks(&now, &programStartTime),
15726                 cps->which, message);
15727         if(serverFP)
15728             fprintf(serverFP, "%ld >%-6s: %s",
15729                 SubtractTimeMarks(&now, &programStartTime),
15730                 cps->which, message), fflush(serverFP);
15731     }
15732
15733     count = strlen(message);
15734     outCount = OutputToProcess(cps->pr, message, count, &error);
15735     if (outCount < count && !exiting
15736                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15737       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15738       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15739         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15740             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15741                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15742                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15743                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15744             } else {
15745                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15746                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15747                 gameInfo.result = res;
15748             }
15749             gameInfo.resultDetails = StrSave(buf);
15750         }
15751         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15752         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15753     }
15754 }
15755
15756 void
15757 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15758 {
15759     char *end_str;
15760     char buf[MSG_SIZ];
15761     ChessProgramState *cps = (ChessProgramState *)closure;
15762
15763     if (isr != cps->isr) return; /* Killed intentionally */
15764     if (count <= 0) {
15765         if (count == 0) {
15766             RemoveInputSource(cps->isr);
15767             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15768                     _(cps->which), cps->program);
15769             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15770             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15771                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15772                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15773                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15774                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15775                 } else {
15776                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15777                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15778                     gameInfo.result = res;
15779                 }
15780                 gameInfo.resultDetails = StrSave(buf);
15781             }
15782             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15783             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15784         } else {
15785             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15786                     _(cps->which), cps->program);
15787             RemoveInputSource(cps->isr);
15788
15789             /* [AS] Program is misbehaving badly... kill it */
15790             if( count == -2 ) {
15791                 DestroyChildProcess( cps->pr, 9 );
15792                 cps->pr = NoProc;
15793             }
15794
15795             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15796         }
15797         return;
15798     }
15799
15800     if ((end_str = strchr(message, '\r')) != NULL)
15801       *end_str = NULLCHAR;
15802     if ((end_str = strchr(message, '\n')) != NULL)
15803       *end_str = NULLCHAR;
15804
15805     if (appData.debugMode) {
15806         TimeMark now; int print = 1;
15807         char *quote = ""; char c; int i;
15808
15809         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15810                 char start = message[0];
15811                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15812                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15813                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15814                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15815                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15816                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15817                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15818                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15819                    sscanf(message, "hint: %c", &c)!=1 &&
15820                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15821                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15822                     print = (appData.engineComments >= 2);
15823                 }
15824                 message[0] = start; // restore original message
15825         }
15826         if(print) {
15827                 GetTimeMark(&now);
15828                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15829                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15830                         quote,
15831                         message);
15832                 if(serverFP)
15833                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15834                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15835                         quote,
15836                         message), fflush(serverFP);
15837         }
15838     }
15839
15840     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15841     if (appData.icsEngineAnalyze) {
15842         if (strstr(message, "whisper") != NULL ||
15843              strstr(message, "kibitz") != NULL ||
15844             strstr(message, "tellics") != NULL) return;
15845     }
15846
15847     HandleMachineMove(message, cps);
15848 }
15849
15850
15851 void
15852 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15853 {
15854     char buf[MSG_SIZ];
15855     int seconds;
15856
15857     if( timeControl_2 > 0 ) {
15858         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15859             tc = timeControl_2;
15860         }
15861     }
15862     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15863     inc /= cps->timeOdds;
15864     st  /= cps->timeOdds;
15865
15866     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15867
15868     if (st > 0) {
15869       /* Set exact time per move, normally using st command */
15870       if (cps->stKludge) {
15871         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15872         seconds = st % 60;
15873         if (seconds == 0) {
15874           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15875         } else {
15876           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15877         }
15878       } else {
15879         snprintf(buf, MSG_SIZ, "st %d\n", st);
15880       }
15881     } else {
15882       /* Set conventional or incremental time control, using level command */
15883       if (seconds == 0) {
15884         /* Note old gnuchess bug -- minutes:seconds used to not work.
15885            Fixed in later versions, but still avoid :seconds
15886            when seconds is 0. */
15887         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15888       } else {
15889         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15890                  seconds, inc/1000.);
15891       }
15892     }
15893     SendToProgram(buf, cps);
15894
15895     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15896     /* Orthogonally, limit search to given depth */
15897     if (sd > 0) {
15898       if (cps->sdKludge) {
15899         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15900       } else {
15901         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15902       }
15903       SendToProgram(buf, cps);
15904     }
15905
15906     if(cps->nps >= 0) { /* [HGM] nps */
15907         if(cps->supportsNPS == FALSE)
15908           cps->nps = -1; // don't use if engine explicitly says not supported!
15909         else {
15910           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15911           SendToProgram(buf, cps);
15912         }
15913     }
15914 }
15915
15916 ChessProgramState *
15917 WhitePlayer ()
15918 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15919 {
15920     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15921        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15922         return &second;
15923     return &first;
15924 }
15925
15926 void
15927 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15928 {
15929     char message[MSG_SIZ];
15930     long time, otime;
15931
15932     /* Note: this routine must be called when the clocks are stopped
15933        or when they have *just* been set or switched; otherwise
15934        it will be off by the time since the current tick started.
15935     */
15936     if (machineWhite) {
15937         time = whiteTimeRemaining / 10;
15938         otime = blackTimeRemaining / 10;
15939     } else {
15940         time = blackTimeRemaining / 10;
15941         otime = whiteTimeRemaining / 10;
15942     }
15943     /* [HGM] translate opponent's time by time-odds factor */
15944     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15945
15946     if (time <= 0) time = 1;
15947     if (otime <= 0) otime = 1;
15948
15949     snprintf(message, MSG_SIZ, "time %ld\n", time);
15950     SendToProgram(message, cps);
15951
15952     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15953     SendToProgram(message, cps);
15954 }
15955
15956 int
15957 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15958 {
15959   char buf[MSG_SIZ];
15960   int len = strlen(name);
15961   int val;
15962
15963   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15964     (*p) += len + 1;
15965     sscanf(*p, "%d", &val);
15966     *loc = (val != 0);
15967     while (**p && **p != ' ')
15968       (*p)++;
15969     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15970     SendToProgram(buf, cps);
15971     return TRUE;
15972   }
15973   return FALSE;
15974 }
15975
15976 int
15977 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15978 {
15979   char buf[MSG_SIZ];
15980   int len = strlen(name);
15981   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15982     (*p) += len + 1;
15983     sscanf(*p, "%d", loc);
15984     while (**p && **p != ' ') (*p)++;
15985     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15986     SendToProgram(buf, cps);
15987     return TRUE;
15988   }
15989   return FALSE;
15990 }
15991
15992 int
15993 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
15994 {
15995   char buf[MSG_SIZ];
15996   int len = strlen(name);
15997   if (strncmp((*p), name, len) == 0
15998       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15999     (*p) += len + 2;
16000     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16001     sscanf(*p, "%[^\"]", *loc);
16002     while (**p && **p != '\"') (*p)++;
16003     if (**p == '\"') (*p)++;
16004     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16005     SendToProgram(buf, cps);
16006     return TRUE;
16007   }
16008   return FALSE;
16009 }
16010
16011 int
16012 ParseOption (Option *opt, ChessProgramState *cps)
16013 // [HGM] options: process the string that defines an engine option, and determine
16014 // name, type, default value, and allowed value range
16015 {
16016         char *p, *q, buf[MSG_SIZ];
16017         int n, min = (-1)<<31, max = 1<<31, def;
16018
16019         if(p = strstr(opt->name, " -spin ")) {
16020             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16021             if(max < min) max = min; // enforce consistency
16022             if(def < min) def = min;
16023             if(def > max) def = max;
16024             opt->value = def;
16025             opt->min = min;
16026             opt->max = max;
16027             opt->type = Spin;
16028         } else if((p = strstr(opt->name, " -slider "))) {
16029             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16030             if((n = sscanf(p, " -slider %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; // Slider;
16038         } else if((p = strstr(opt->name, " -string "))) {
16039             opt->textValue = p+9;
16040             opt->type = TextBox;
16041         } else if((p = strstr(opt->name, " -file "))) {
16042             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16043             opt->textValue = p+7;
16044             opt->type = FileName; // FileName;
16045         } else if((p = strstr(opt->name, " -path "))) {
16046             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16047             opt->textValue = p+7;
16048             opt->type = PathName; // PathName;
16049         } else if(p = strstr(opt->name, " -check ")) {
16050             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16051             opt->value = (def != 0);
16052             opt->type = CheckBox;
16053         } else if(p = strstr(opt->name, " -combo ")) {
16054             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16055             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16056             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16057             opt->value = n = 0;
16058             while(q = StrStr(q, " /// ")) {
16059                 n++; *q = 0;    // count choices, and null-terminate each of them
16060                 q += 5;
16061                 if(*q == '*') { // remember default, which is marked with * prefix
16062                     q++;
16063                     opt->value = n;
16064                 }
16065                 cps->comboList[cps->comboCnt++] = q;
16066             }
16067             cps->comboList[cps->comboCnt++] = NULL;
16068             opt->max = n + 1;
16069             opt->type = ComboBox;
16070         } else if(p = strstr(opt->name, " -button")) {
16071             opt->type = Button;
16072         } else if(p = strstr(opt->name, " -save")) {
16073             opt->type = SaveButton;
16074         } else return FALSE;
16075         *p = 0; // terminate option name
16076         // now look if the command-line options define a setting for this engine option.
16077         if(cps->optionSettings && cps->optionSettings[0])
16078             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16079         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16080           snprintf(buf, MSG_SIZ, "option %s", p);
16081                 if(p = strstr(buf, ",")) *p = 0;
16082                 if(q = strchr(buf, '=')) switch(opt->type) {
16083                     case ComboBox:
16084                         for(n=0; n<opt->max; n++)
16085                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16086                         break;
16087                     case TextBox:
16088                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16089                         break;
16090                     case Spin:
16091                     case CheckBox:
16092                         opt->value = atoi(q+1);
16093                     default:
16094                         break;
16095                 }
16096                 strcat(buf, "\n");
16097                 SendToProgram(buf, cps);
16098         }
16099         return TRUE;
16100 }
16101
16102 void
16103 FeatureDone (ChessProgramState *cps, int val)
16104 {
16105   DelayedEventCallback cb = GetDelayedEvent();
16106   if ((cb == InitBackEnd3 && cps == &first) ||
16107       (cb == SettingsMenuIfReady && cps == &second) ||
16108       (cb == LoadEngine) ||
16109       (cb == TwoMachinesEventIfReady)) {
16110     CancelDelayedEvent();
16111     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16112   }
16113   cps->initDone = val;
16114   if(val) cps->reload = FALSE;
16115 }
16116
16117 /* Parse feature command from engine */
16118 void
16119 ParseFeatures (char *args, ChessProgramState *cps)
16120 {
16121   char *p = args;
16122   char *q = NULL;
16123   int val;
16124   char buf[MSG_SIZ];
16125
16126   for (;;) {
16127     while (*p == ' ') p++;
16128     if (*p == NULLCHAR) return;
16129
16130     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16131     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16132     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16133     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16134     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16135     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16136     if (BoolFeature(&p, "reuse", &val, cps)) {
16137       /* Engine can disable reuse, but can't enable it if user said no */
16138       if (!val) cps->reuse = FALSE;
16139       continue;
16140     }
16141     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16142     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16143       if (gameMode == TwoMachinesPlay) {
16144         DisplayTwoMachinesTitle();
16145       } else {
16146         DisplayTitle("");
16147       }
16148       continue;
16149     }
16150     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16151     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16152     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16153     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16154     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16155     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16156     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16157     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16158     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16159     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16160     if (IntFeature(&p, "done", &val, cps)) {
16161       FeatureDone(cps, val);
16162       continue;
16163     }
16164     /* Added by Tord: */
16165     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16166     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16167     /* End of additions by Tord */
16168
16169     /* [HGM] added features: */
16170     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16171     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16172     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16173     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16174     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16175     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16176     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16177         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16178         FREE(cps->option[cps->nrOptions].name);
16179         cps->option[cps->nrOptions].name = q; q = NULL;
16180         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16181           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16182             SendToProgram(buf, cps);
16183             continue;
16184         }
16185         if(cps->nrOptions >= MAX_OPTIONS) {
16186             cps->nrOptions--;
16187             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16188             DisplayError(buf, 0);
16189         }
16190         continue;
16191     }
16192     /* End of additions by HGM */
16193
16194     /* unknown feature: complain and skip */
16195     q = p;
16196     while (*q && *q != '=') q++;
16197     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16198     SendToProgram(buf, cps);
16199     p = q;
16200     if (*p == '=') {
16201       p++;
16202       if (*p == '\"') {
16203         p++;
16204         while (*p && *p != '\"') p++;
16205         if (*p == '\"') p++;
16206       } else {
16207         while (*p && *p != ' ') p++;
16208       }
16209     }
16210   }
16211
16212 }
16213
16214 void
16215 PeriodicUpdatesEvent (int newState)
16216 {
16217     if (newState == appData.periodicUpdates)
16218       return;
16219
16220     appData.periodicUpdates=newState;
16221
16222     /* Display type changes, so update it now */
16223 //    DisplayAnalysis();
16224
16225     /* Get the ball rolling again... */
16226     if (newState) {
16227         AnalysisPeriodicEvent(1);
16228         StartAnalysisClock();
16229     }
16230 }
16231
16232 void
16233 PonderNextMoveEvent (int newState)
16234 {
16235     if (newState == appData.ponderNextMove) return;
16236     if (gameMode == EditPosition) EditPositionDone(TRUE);
16237     if (newState) {
16238         SendToProgram("hard\n", &first);
16239         if (gameMode == TwoMachinesPlay) {
16240             SendToProgram("hard\n", &second);
16241         }
16242     } else {
16243         SendToProgram("easy\n", &first);
16244         thinkOutput[0] = NULLCHAR;
16245         if (gameMode == TwoMachinesPlay) {
16246             SendToProgram("easy\n", &second);
16247         }
16248     }
16249     appData.ponderNextMove = newState;
16250 }
16251
16252 void
16253 NewSettingEvent (int option, int *feature, char *command, int value)
16254 {
16255     char buf[MSG_SIZ];
16256
16257     if (gameMode == EditPosition) EditPositionDone(TRUE);
16258     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16259     if(feature == NULL || *feature) SendToProgram(buf, &first);
16260     if (gameMode == TwoMachinesPlay) {
16261         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16262     }
16263 }
16264
16265 void
16266 ShowThinkingEvent ()
16267 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16268 {
16269     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16270     int newState = appData.showThinking
16271         // [HGM] thinking: other features now need thinking output as well
16272         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16273
16274     if (oldState == newState) return;
16275     oldState = newState;
16276     if (gameMode == EditPosition) EditPositionDone(TRUE);
16277     if (oldState) {
16278         SendToProgram("post\n", &first);
16279         if (gameMode == TwoMachinesPlay) {
16280             SendToProgram("post\n", &second);
16281         }
16282     } else {
16283         SendToProgram("nopost\n", &first);
16284         thinkOutput[0] = NULLCHAR;
16285         if (gameMode == TwoMachinesPlay) {
16286             SendToProgram("nopost\n", &second);
16287         }
16288     }
16289 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16290 }
16291
16292 void
16293 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16294 {
16295   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16296   if (pr == NoProc) return;
16297   AskQuestion(title, question, replyPrefix, pr);
16298 }
16299
16300 void
16301 TypeInEvent (char firstChar)
16302 {
16303     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16304         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16305         gameMode == AnalyzeMode || gameMode == EditGame ||
16306         gameMode == EditPosition || gameMode == IcsExamining ||
16307         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16308         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16309                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16310                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16311         gameMode == Training) PopUpMoveDialog(firstChar);
16312 }
16313
16314 void
16315 TypeInDoneEvent (char *move)
16316 {
16317         Board board;
16318         int n, fromX, fromY, toX, toY;
16319         char promoChar;
16320         ChessMove moveType;
16321
16322         // [HGM] FENedit
16323         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16324                 EditPositionPasteFEN(move);
16325                 return;
16326         }
16327         // [HGM] movenum: allow move number to be typed in any mode
16328         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16329           ToNrEvent(2*n-1);
16330           return;
16331         }
16332         // undocumented kludge: allow command-line option to be typed in!
16333         // (potentially fatal, and does not implement the effect of the option.)
16334         // should only be used for options that are values on which future decisions will be made,
16335         // and definitely not on options that would be used during initialization.
16336         if(strstr(move, "!!! -") == move) {
16337             ParseArgsFromString(move+4);
16338             return;
16339         }
16340
16341       if (gameMode != EditGame && currentMove != forwardMostMove &&
16342         gameMode != Training) {
16343         DisplayMoveError(_("Displayed move is not current"));
16344       } else {
16345         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16346           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16347         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16348         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16349           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16350           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16351         } else {
16352           DisplayMoveError(_("Could not parse move"));
16353         }
16354       }
16355 }
16356
16357 void
16358 DisplayMove (int moveNumber)
16359 {
16360     char message[MSG_SIZ];
16361     char res[MSG_SIZ];
16362     char cpThinkOutput[MSG_SIZ];
16363
16364     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16365
16366     if (moveNumber == forwardMostMove - 1 ||
16367         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16368
16369         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16370
16371         if (strchr(cpThinkOutput, '\n')) {
16372             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16373         }
16374     } else {
16375         *cpThinkOutput = NULLCHAR;
16376     }
16377
16378     /* [AS] Hide thinking from human user */
16379     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16380         *cpThinkOutput = NULLCHAR;
16381         if( thinkOutput[0] != NULLCHAR ) {
16382             int i;
16383
16384             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16385                 cpThinkOutput[i] = '.';
16386             }
16387             cpThinkOutput[i] = NULLCHAR;
16388             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16389         }
16390     }
16391
16392     if (moveNumber == forwardMostMove - 1 &&
16393         gameInfo.resultDetails != NULL) {
16394         if (gameInfo.resultDetails[0] == NULLCHAR) {
16395           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16396         } else {
16397           snprintf(res, MSG_SIZ, " {%s} %s",
16398                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16399         }
16400     } else {
16401         res[0] = NULLCHAR;
16402     }
16403
16404     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16405         DisplayMessage(res, cpThinkOutput);
16406     } else {
16407       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16408                 WhiteOnMove(moveNumber) ? " " : ".. ",
16409                 parseList[moveNumber], res);
16410         DisplayMessage(message, cpThinkOutput);
16411     }
16412 }
16413
16414 void
16415 DisplayComment (int moveNumber, char *text)
16416 {
16417     char title[MSG_SIZ];
16418
16419     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16420       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16421     } else {
16422       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16423               WhiteOnMove(moveNumber) ? " " : ".. ",
16424               parseList[moveNumber]);
16425     }
16426     if (text != NULL && (appData.autoDisplayComment || commentUp))
16427         CommentPopUp(title, text);
16428 }
16429
16430 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16431  * might be busy thinking or pondering.  It can be omitted if your
16432  * gnuchess is configured to stop thinking immediately on any user
16433  * input.  However, that gnuchess feature depends on the FIONREAD
16434  * ioctl, which does not work properly on some flavors of Unix.
16435  */
16436 void
16437 Attention (ChessProgramState *cps)
16438 {
16439 #if ATTENTION
16440     if (!cps->useSigint) return;
16441     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16442     switch (gameMode) {
16443       case MachinePlaysWhite:
16444       case MachinePlaysBlack:
16445       case TwoMachinesPlay:
16446       case IcsPlayingWhite:
16447       case IcsPlayingBlack:
16448       case AnalyzeMode:
16449       case AnalyzeFile:
16450         /* Skip if we know it isn't thinking */
16451         if (!cps->maybeThinking) return;
16452         if (appData.debugMode)
16453           fprintf(debugFP, "Interrupting %s\n", cps->which);
16454         InterruptChildProcess(cps->pr);
16455         cps->maybeThinking = FALSE;
16456         break;
16457       default:
16458         break;
16459     }
16460 #endif /*ATTENTION*/
16461 }
16462
16463 int
16464 CheckFlags ()
16465 {
16466     if (whiteTimeRemaining <= 0) {
16467         if (!whiteFlag) {
16468             whiteFlag = TRUE;
16469             if (appData.icsActive) {
16470                 if (appData.autoCallFlag &&
16471                     gameMode == IcsPlayingBlack && !blackFlag) {
16472                   SendToICS(ics_prefix);
16473                   SendToICS("flag\n");
16474                 }
16475             } else {
16476                 if (blackFlag) {
16477                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16478                 } else {
16479                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16480                     if (appData.autoCallFlag) {
16481                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16482                         return TRUE;
16483                     }
16484                 }
16485             }
16486         }
16487     }
16488     if (blackTimeRemaining <= 0) {
16489         if (!blackFlag) {
16490             blackFlag = TRUE;
16491             if (appData.icsActive) {
16492                 if (appData.autoCallFlag &&
16493                     gameMode == IcsPlayingWhite && !whiteFlag) {
16494                   SendToICS(ics_prefix);
16495                   SendToICS("flag\n");
16496                 }
16497             } else {
16498                 if (whiteFlag) {
16499                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16500                 } else {
16501                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16502                     if (appData.autoCallFlag) {
16503                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16504                         return TRUE;
16505                     }
16506                 }
16507             }
16508         }
16509     }
16510     return FALSE;
16511 }
16512
16513 void
16514 CheckTimeControl ()
16515 {
16516     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16517         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16518
16519     /*
16520      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16521      */
16522     if ( !WhiteOnMove(forwardMostMove) ) {
16523         /* White made time control */
16524         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16525         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16526         /* [HGM] time odds: correct new time quota for time odds! */
16527                                             / WhitePlayer()->timeOdds;
16528         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16529     } else {
16530         lastBlack -= blackTimeRemaining;
16531         /* Black made time control */
16532         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16533                                             / WhitePlayer()->other->timeOdds;
16534         lastWhite = whiteTimeRemaining;
16535     }
16536 }
16537
16538 void
16539 DisplayBothClocks ()
16540 {
16541     int wom = gameMode == EditPosition ?
16542       !blackPlaysFirst : WhiteOnMove(currentMove);
16543     DisplayWhiteClock(whiteTimeRemaining, wom);
16544     DisplayBlackClock(blackTimeRemaining, !wom);
16545 }
16546
16547
16548 /* Timekeeping seems to be a portability nightmare.  I think everyone
16549    has ftime(), but I'm really not sure, so I'm including some ifdefs
16550    to use other calls if you don't.  Clocks will be less accurate if
16551    you have neither ftime nor gettimeofday.
16552 */
16553
16554 /* VS 2008 requires the #include outside of the function */
16555 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16556 #include <sys/timeb.h>
16557 #endif
16558
16559 /* Get the current time as a TimeMark */
16560 void
16561 GetTimeMark (TimeMark *tm)
16562 {
16563 #if HAVE_GETTIMEOFDAY
16564
16565     struct timeval timeVal;
16566     struct timezone timeZone;
16567
16568     gettimeofday(&timeVal, &timeZone);
16569     tm->sec = (long) timeVal.tv_sec;
16570     tm->ms = (int) (timeVal.tv_usec / 1000L);
16571
16572 #else /*!HAVE_GETTIMEOFDAY*/
16573 #if HAVE_FTIME
16574
16575 // include <sys/timeb.h> / moved to just above start of function
16576     struct timeb timeB;
16577
16578     ftime(&timeB);
16579     tm->sec = (long) timeB.time;
16580     tm->ms = (int) timeB.millitm;
16581
16582 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16583     tm->sec = (long) time(NULL);
16584     tm->ms = 0;
16585 #endif
16586 #endif
16587 }
16588
16589 /* Return the difference in milliseconds between two
16590    time marks.  We assume the difference will fit in a long!
16591 */
16592 long
16593 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16594 {
16595     return 1000L*(tm2->sec - tm1->sec) +
16596            (long) (tm2->ms - tm1->ms);
16597 }
16598
16599
16600 /*
16601  * Code to manage the game clocks.
16602  *
16603  * In tournament play, black starts the clock and then white makes a move.
16604  * We give the human user a slight advantage if he is playing white---the
16605  * clocks don't run until he makes his first move, so it takes zero time.
16606  * Also, we don't account for network lag, so we could get out of sync
16607  * with GNU Chess's clock -- but then, referees are always right.
16608  */
16609
16610 static TimeMark tickStartTM;
16611 static long intendedTickLength;
16612
16613 long
16614 NextTickLength (long timeRemaining)
16615 {
16616     long nominalTickLength, nextTickLength;
16617
16618     if (timeRemaining > 0L && timeRemaining <= 10000L)
16619       nominalTickLength = 100L;
16620     else
16621       nominalTickLength = 1000L;
16622     nextTickLength = timeRemaining % nominalTickLength;
16623     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16624
16625     return nextTickLength;
16626 }
16627
16628 /* Adjust clock one minute up or down */
16629 void
16630 AdjustClock (Boolean which, int dir)
16631 {
16632     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16633     if(which) blackTimeRemaining += 60000*dir;
16634     else      whiteTimeRemaining += 60000*dir;
16635     DisplayBothClocks();
16636     adjustedClock = TRUE;
16637 }
16638
16639 /* Stop clocks and reset to a fresh time control */
16640 void
16641 ResetClocks ()
16642 {
16643     (void) StopClockTimer();
16644     if (appData.icsActive) {
16645         whiteTimeRemaining = blackTimeRemaining = 0;
16646     } else if (searchTime) {
16647         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16648         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16649     } else { /* [HGM] correct new time quote for time odds */
16650         whiteTC = blackTC = fullTimeControlString;
16651         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16652         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16653     }
16654     if (whiteFlag || blackFlag) {
16655         DisplayTitle("");
16656         whiteFlag = blackFlag = FALSE;
16657     }
16658     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16659     DisplayBothClocks();
16660     adjustedClock = FALSE;
16661 }
16662
16663 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16664
16665 /* Decrement running clock by amount of time that has passed */
16666 void
16667 DecrementClocks ()
16668 {
16669     long timeRemaining;
16670     long lastTickLength, fudge;
16671     TimeMark now;
16672
16673     if (!appData.clockMode) return;
16674     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16675
16676     GetTimeMark(&now);
16677
16678     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16679
16680     /* Fudge if we woke up a little too soon */
16681     fudge = intendedTickLength - lastTickLength;
16682     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16683
16684     if (WhiteOnMove(forwardMostMove)) {
16685         if(whiteNPS >= 0) lastTickLength = 0;
16686         timeRemaining = whiteTimeRemaining -= lastTickLength;
16687         if(timeRemaining < 0 && !appData.icsActive) {
16688             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16689             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16690                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16691                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16692             }
16693         }
16694         DisplayWhiteClock(whiteTimeRemaining - fudge,
16695                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16696     } else {
16697         if(blackNPS >= 0) lastTickLength = 0;
16698         timeRemaining = blackTimeRemaining -= lastTickLength;
16699         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16700             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16701             if(suddenDeath) {
16702                 blackStartMove = forwardMostMove;
16703                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16704             }
16705         }
16706         DisplayBlackClock(blackTimeRemaining - fudge,
16707                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16708     }
16709     if (CheckFlags()) return;
16710
16711     if(twoBoards) { // count down secondary board's clocks as well
16712         activePartnerTime -= lastTickLength;
16713         partnerUp = 1;
16714         if(activePartner == 'W')
16715             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16716         else
16717             DisplayBlackClock(activePartnerTime, TRUE);
16718         partnerUp = 0;
16719     }
16720
16721     tickStartTM = now;
16722     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16723     StartClockTimer(intendedTickLength);
16724
16725     /* if the time remaining has fallen below the alarm threshold, sound the
16726      * alarm. if the alarm has sounded and (due to a takeback or time control
16727      * with increment) the time remaining has increased to a level above the
16728      * threshold, reset the alarm so it can sound again.
16729      */
16730
16731     if (appData.icsActive && appData.icsAlarm) {
16732
16733         /* make sure we are dealing with the user's clock */
16734         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16735                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16736            )) return;
16737
16738         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16739             alarmSounded = FALSE;
16740         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16741             PlayAlarmSound();
16742             alarmSounded = TRUE;
16743         }
16744     }
16745 }
16746
16747
16748 /* A player has just moved, so stop the previously running
16749    clock and (if in clock mode) start the other one.
16750    We redisplay both clocks in case we're in ICS mode, because
16751    ICS gives us an update to both clocks after every move.
16752    Note that this routine is called *after* forwardMostMove
16753    is updated, so the last fractional tick must be subtracted
16754    from the color that is *not* on move now.
16755 */
16756 void
16757 SwitchClocks (int newMoveNr)
16758 {
16759     long lastTickLength;
16760     TimeMark now;
16761     int flagged = FALSE;
16762
16763     GetTimeMark(&now);
16764
16765     if (StopClockTimer() && appData.clockMode) {
16766         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16767         if (!WhiteOnMove(forwardMostMove)) {
16768             if(blackNPS >= 0) lastTickLength = 0;
16769             blackTimeRemaining -= lastTickLength;
16770            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16771 //         if(pvInfoList[forwardMostMove].time == -1)
16772                  pvInfoList[forwardMostMove].time =               // use GUI time
16773                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16774         } else {
16775            if(whiteNPS >= 0) lastTickLength = 0;
16776            whiteTimeRemaining -= lastTickLength;
16777            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16778 //         if(pvInfoList[forwardMostMove].time == -1)
16779                  pvInfoList[forwardMostMove].time =
16780                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16781         }
16782         flagged = CheckFlags();
16783     }
16784     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16785     CheckTimeControl();
16786
16787     if (flagged || !appData.clockMode) return;
16788
16789     switch (gameMode) {
16790       case MachinePlaysBlack:
16791       case MachinePlaysWhite:
16792       case BeginningOfGame:
16793         if (pausing) return;
16794         break;
16795
16796       case EditGame:
16797       case PlayFromGameFile:
16798       case IcsExamining:
16799         return;
16800
16801       default:
16802         break;
16803     }
16804
16805     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16806         if(WhiteOnMove(forwardMostMove))
16807              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16808         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16809     }
16810
16811     tickStartTM = now;
16812     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16813       whiteTimeRemaining : blackTimeRemaining);
16814     StartClockTimer(intendedTickLength);
16815 }
16816
16817
16818 /* Stop both clocks */
16819 void
16820 StopClocks ()
16821 {
16822     long lastTickLength;
16823     TimeMark now;
16824
16825     if (!StopClockTimer()) return;
16826     if (!appData.clockMode) return;
16827
16828     GetTimeMark(&now);
16829
16830     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16831     if (WhiteOnMove(forwardMostMove)) {
16832         if(whiteNPS >= 0) lastTickLength = 0;
16833         whiteTimeRemaining -= lastTickLength;
16834         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16835     } else {
16836         if(blackNPS >= 0) lastTickLength = 0;
16837         blackTimeRemaining -= lastTickLength;
16838         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16839     }
16840     CheckFlags();
16841 }
16842
16843 /* Start clock of player on move.  Time may have been reset, so
16844    if clock is already running, stop and restart it. */
16845 void
16846 StartClocks ()
16847 {
16848     (void) StopClockTimer(); /* in case it was running already */
16849     DisplayBothClocks();
16850     if (CheckFlags()) return;
16851
16852     if (!appData.clockMode) return;
16853     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16854
16855     GetTimeMark(&tickStartTM);
16856     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16857       whiteTimeRemaining : blackTimeRemaining);
16858
16859    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16860     whiteNPS = blackNPS = -1;
16861     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16862        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16863         whiteNPS = first.nps;
16864     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16865        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16866         blackNPS = first.nps;
16867     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16868         whiteNPS = second.nps;
16869     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16870         blackNPS = second.nps;
16871     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16872
16873     StartClockTimer(intendedTickLength);
16874 }
16875
16876 char *
16877 TimeString (long ms)
16878 {
16879     long second, minute, hour, day;
16880     char *sign = "";
16881     static char buf[32];
16882
16883     if (ms > 0 && ms <= 9900) {
16884       /* convert milliseconds to tenths, rounding up */
16885       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16886
16887       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16888       return buf;
16889     }
16890
16891     /* convert milliseconds to seconds, rounding up */
16892     /* use floating point to avoid strangeness of integer division
16893        with negative dividends on many machines */
16894     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16895
16896     if (second < 0) {
16897         sign = "-";
16898         second = -second;
16899     }
16900
16901     day = second / (60 * 60 * 24);
16902     second = second % (60 * 60 * 24);
16903     hour = second / (60 * 60);
16904     second = second % (60 * 60);
16905     minute = second / 60;
16906     second = second % 60;
16907
16908     if (day > 0)
16909       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16910               sign, day, hour, minute, second);
16911     else if (hour > 0)
16912       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16913     else
16914       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16915
16916     return buf;
16917 }
16918
16919
16920 /*
16921  * This is necessary because some C libraries aren't ANSI C compliant yet.
16922  */
16923 char *
16924 StrStr (char *string, char *match)
16925 {
16926     int i, length;
16927
16928     length = strlen(match);
16929
16930     for (i = strlen(string) - length; i >= 0; i--, string++)
16931       if (!strncmp(match, string, length))
16932         return string;
16933
16934     return NULL;
16935 }
16936
16937 char *
16938 StrCaseStr (char *string, char *match)
16939 {
16940     int i, j, length;
16941
16942     length = strlen(match);
16943
16944     for (i = strlen(string) - length; i >= 0; i--, string++) {
16945         for (j = 0; j < length; j++) {
16946             if (ToLower(match[j]) != ToLower(string[j]))
16947               break;
16948         }
16949         if (j == length) return string;
16950     }
16951
16952     return NULL;
16953 }
16954
16955 #ifndef _amigados
16956 int
16957 StrCaseCmp (char *s1, char *s2)
16958 {
16959     char c1, c2;
16960
16961     for (;;) {
16962         c1 = ToLower(*s1++);
16963         c2 = ToLower(*s2++);
16964         if (c1 > c2) return 1;
16965         if (c1 < c2) return -1;
16966         if (c1 == NULLCHAR) return 0;
16967     }
16968 }
16969
16970
16971 int
16972 ToLower (int c)
16973 {
16974     return isupper(c) ? tolower(c) : c;
16975 }
16976
16977
16978 int
16979 ToUpper (int c)
16980 {
16981     return islower(c) ? toupper(c) : c;
16982 }
16983 #endif /* !_amigados    */
16984
16985 char *
16986 StrSave (char *s)
16987 {
16988   char *ret;
16989
16990   if ((ret = (char *) malloc(strlen(s) + 1)))
16991     {
16992       safeStrCpy(ret, s, strlen(s)+1);
16993     }
16994   return ret;
16995 }
16996
16997 char *
16998 StrSavePtr (char *s, char **savePtr)
16999 {
17000     if (*savePtr) {
17001         free(*savePtr);
17002     }
17003     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17004       safeStrCpy(*savePtr, s, strlen(s)+1);
17005     }
17006     return(*savePtr);
17007 }
17008
17009 char *
17010 PGNDate ()
17011 {
17012     time_t clock;
17013     struct tm *tm;
17014     char buf[MSG_SIZ];
17015
17016     clock = time((time_t *)NULL);
17017     tm = localtime(&clock);
17018     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17019             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17020     return StrSave(buf);
17021 }
17022
17023
17024 char *
17025 PositionToFEN (int move, char *overrideCastling)
17026 {
17027     int i, j, fromX, fromY, toX, toY;
17028     int whiteToPlay;
17029     char buf[MSG_SIZ];
17030     char *p, *q;
17031     int emptycount;
17032     ChessSquare piece;
17033
17034     whiteToPlay = (gameMode == EditPosition) ?
17035       !blackPlaysFirst : (move % 2 == 0);
17036     p = buf;
17037
17038     /* Piece placement data */
17039     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17040         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17041         emptycount = 0;
17042         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17043             if (boards[move][i][j] == EmptySquare) {
17044                 emptycount++;
17045             } else { ChessSquare piece = boards[move][i][j];
17046                 if (emptycount > 0) {
17047                     if(emptycount<10) /* [HGM] can be >= 10 */
17048                         *p++ = '0' + emptycount;
17049                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17050                     emptycount = 0;
17051                 }
17052                 if(PieceToChar(piece) == '+') {
17053                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17054                     *p++ = '+';
17055                     piece = (ChessSquare)(DEMOTED piece);
17056                 }
17057                 *p++ = PieceToChar(piece);
17058                 if(p[-1] == '~') {
17059                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17060                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17061                     *p++ = '~';
17062                 }
17063             }
17064         }
17065         if (emptycount > 0) {
17066             if(emptycount<10) /* [HGM] can be >= 10 */
17067                 *p++ = '0' + emptycount;
17068             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17069             emptycount = 0;
17070         }
17071         *p++ = '/';
17072     }
17073     *(p - 1) = ' ';
17074
17075     /* [HGM] print Crazyhouse or Shogi holdings */
17076     if( gameInfo.holdingsWidth ) {
17077         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17078         q = p;
17079         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17080             piece = boards[move][i][BOARD_WIDTH-1];
17081             if( piece != EmptySquare )
17082               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17083                   *p++ = PieceToChar(piece);
17084         }
17085         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17086             piece = boards[move][BOARD_HEIGHT-i-1][0];
17087             if( piece != EmptySquare )
17088               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17089                   *p++ = PieceToChar(piece);
17090         }
17091
17092         if( q == p ) *p++ = '-';
17093         *p++ = ']';
17094         *p++ = ' ';
17095     }
17096
17097     /* Active color */
17098     *p++ = whiteToPlay ? 'w' : 'b';
17099     *p++ = ' ';
17100
17101   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17102     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17103   } else {
17104   if(nrCastlingRights) {
17105      q = p;
17106      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17107        /* [HGM] write directly from rights */
17108            if(boards[move][CASTLING][2] != NoRights &&
17109               boards[move][CASTLING][0] != NoRights   )
17110                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17111            if(boards[move][CASTLING][2] != NoRights &&
17112               boards[move][CASTLING][1] != NoRights   )
17113                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17114            if(boards[move][CASTLING][5] != NoRights &&
17115               boards[move][CASTLING][3] != NoRights   )
17116                 *p++ = boards[move][CASTLING][3] + AAA;
17117            if(boards[move][CASTLING][5] != NoRights &&
17118               boards[move][CASTLING][4] != NoRights   )
17119                 *p++ = boards[move][CASTLING][4] + AAA;
17120      } else {
17121
17122         /* [HGM] write true castling rights */
17123         if( nrCastlingRights == 6 ) {
17124             int q, k=0;
17125             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17126                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17127             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17128                  boards[move][CASTLING][2] != NoRights  );
17129             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17130                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17131                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17132                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17133                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17134             }
17135             if(q) *p++ = 'Q';
17136             k = 0;
17137             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17138                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17139             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17140                  boards[move][CASTLING][5] != NoRights  );
17141             if(gameInfo.variant == VariantSChess) {
17142                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17143                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17144                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17145                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17146             }
17147             if(q) *p++ = 'q';
17148         }
17149      }
17150      if (q == p) *p++ = '-'; /* No castling rights */
17151      *p++ = ' ';
17152   }
17153
17154   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17155      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17156     /* En passant target square */
17157     if (move > backwardMostMove) {
17158         fromX = moveList[move - 1][0] - AAA;
17159         fromY = moveList[move - 1][1] - ONE;
17160         toX = moveList[move - 1][2] - AAA;
17161         toY = moveList[move - 1][3] - ONE;
17162         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17163             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17164             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17165             fromX == toX) {
17166             /* 2-square pawn move just happened */
17167             *p++ = toX + AAA;
17168             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17169         } else {
17170             *p++ = '-';
17171         }
17172     } else if(move == backwardMostMove) {
17173         // [HGM] perhaps we should always do it like this, and forget the above?
17174         if((signed char)boards[move][EP_STATUS] >= 0) {
17175             *p++ = boards[move][EP_STATUS] + AAA;
17176             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17177         } else {
17178             *p++ = '-';
17179         }
17180     } else {
17181         *p++ = '-';
17182     }
17183     *p++ = ' ';
17184   }
17185   }
17186
17187     /* [HGM] find reversible plies */
17188     {   int i = 0, j=move;
17189
17190         if (appData.debugMode) { int k;
17191             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17192             for(k=backwardMostMove; k<=forwardMostMove; k++)
17193                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17194
17195         }
17196
17197         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17198         if( j == backwardMostMove ) i += initialRulePlies;
17199         sprintf(p, "%d ", i);
17200         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17201     }
17202     /* Fullmove number */
17203     sprintf(p, "%d", (move / 2) + 1);
17204
17205     return StrSave(buf);
17206 }
17207
17208 Boolean
17209 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17210 {
17211     int i, j;
17212     char *p, c;
17213     int emptycount, virgin[BOARD_FILES];
17214     ChessSquare piece;
17215
17216     p = fen;
17217
17218     /* [HGM] by default clear Crazyhouse holdings, if present */
17219     if(gameInfo.holdingsWidth) {
17220        for(i=0; i<BOARD_HEIGHT; i++) {
17221            board[i][0]             = EmptySquare; /* black holdings */
17222            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17223            board[i][1]             = (ChessSquare) 0; /* black counts */
17224            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17225        }
17226     }
17227
17228     /* Piece placement data */
17229     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17230         j = 0;
17231         for (;;) {
17232             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17233                 if (*p == '/') p++;
17234                 emptycount = gameInfo.boardWidth - j;
17235                 while (emptycount--)
17236                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17237                 break;
17238 #if(BOARD_FILES >= 10)
17239             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17240                 p++; emptycount=10;
17241                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17242                 while (emptycount--)
17243                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17244 #endif
17245             } else if (isdigit(*p)) {
17246                 emptycount = *p++ - '0';
17247                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17248                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17249                 while (emptycount--)
17250                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17251             } else if (*p == '+' || isalpha(*p)) {
17252                 if (j >= gameInfo.boardWidth) return FALSE;
17253                 if(*p=='+') {
17254                     piece = CharToPiece(*++p);
17255                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17256                     piece = (ChessSquare) (PROMOTED piece ); p++;
17257                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17258                 } else piece = CharToPiece(*p++);
17259
17260                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17261                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17262                     piece = (ChessSquare) (PROMOTED piece);
17263                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17264                     p++;
17265                 }
17266                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17267             } else {
17268                 return FALSE;
17269             }
17270         }
17271     }
17272     while (*p == '/' || *p == ' ') p++;
17273
17274     /* [HGM] look for Crazyhouse holdings here */
17275     while(*p==' ') p++;
17276     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17277         if(*p == '[') p++;
17278         if(*p == '-' ) p++; /* empty holdings */ else {
17279             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17280             /* if we would allow FEN reading to set board size, we would   */
17281             /* have to add holdings and shift the board read so far here   */
17282             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17283                 p++;
17284                 if((int) piece >= (int) BlackPawn ) {
17285                     i = (int)piece - (int)BlackPawn;
17286                     i = PieceToNumber((ChessSquare)i);
17287                     if( i >= gameInfo.holdingsSize ) return FALSE;
17288                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17289                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17290                 } else {
17291                     i = (int)piece - (int)WhitePawn;
17292                     i = PieceToNumber((ChessSquare)i);
17293                     if( i >= gameInfo.holdingsSize ) return FALSE;
17294                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17295                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17296                 }
17297             }
17298         }
17299         if(*p == ']') p++;
17300     }
17301
17302     while(*p == ' ') p++;
17303
17304     /* Active color */
17305     c = *p++;
17306     if(appData.colorNickNames) {
17307       if( c == appData.colorNickNames[0] ) c = 'w'; else
17308       if( c == appData.colorNickNames[1] ) c = 'b';
17309     }
17310     switch (c) {
17311       case 'w':
17312         *blackPlaysFirst = FALSE;
17313         break;
17314       case 'b':
17315         *blackPlaysFirst = TRUE;
17316         break;
17317       default:
17318         return FALSE;
17319     }
17320
17321     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17322     /* return the extra info in global variiables             */
17323
17324     /* set defaults in case FEN is incomplete */
17325     board[EP_STATUS] = EP_UNKNOWN;
17326     for(i=0; i<nrCastlingRights; i++ ) {
17327         board[CASTLING][i] =
17328             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17329     }   /* assume possible unless obviously impossible */
17330     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17331     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17332     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17333                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17334     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17335     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17336     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17337                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17338     FENrulePlies = 0;
17339
17340     while(*p==' ') p++;
17341     if(nrCastlingRights) {
17342       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17343       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17344           /* castling indicator present, so default becomes no castlings */
17345           for(i=0; i<nrCastlingRights; i++ ) {
17346                  board[CASTLING][i] = NoRights;
17347           }
17348       }
17349       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17350              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17351              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17352              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17353         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17354
17355         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17356             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17357             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17358         }
17359         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17360             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17361         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17362                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17363         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17364                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17365         switch(c) {
17366           case'K':
17367               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17368               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17369               board[CASTLING][2] = whiteKingFile;
17370               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17371               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17372               break;
17373           case'Q':
17374               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17375               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17376               board[CASTLING][2] = whiteKingFile;
17377               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17378               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17379               break;
17380           case'k':
17381               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17382               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17383               board[CASTLING][5] = blackKingFile;
17384               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17385               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17386               break;
17387           case'q':
17388               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17389               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17390               board[CASTLING][5] = blackKingFile;
17391               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17392               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17393           case '-':
17394               break;
17395           default: /* FRC castlings */
17396               if(c >= 'a') { /* black rights */
17397                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17398                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17399                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17400                   if(i == BOARD_RGHT) break;
17401                   board[CASTLING][5] = i;
17402                   c -= AAA;
17403                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17404                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17405                   if(c > i)
17406                       board[CASTLING][3] = c;
17407                   else
17408                       board[CASTLING][4] = c;
17409               } else { /* white rights */
17410                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17411                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17412                     if(board[0][i] == WhiteKing) break;
17413                   if(i == BOARD_RGHT) break;
17414                   board[CASTLING][2] = i;
17415                   c -= AAA - 'a' + 'A';
17416                   if(board[0][c] >= WhiteKing) break;
17417                   if(c > i)
17418                       board[CASTLING][0] = c;
17419                   else
17420                       board[CASTLING][1] = c;
17421               }
17422         }
17423       }
17424       for(i=0; i<nrCastlingRights; i++)
17425         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17426       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17427     if (appData.debugMode) {
17428         fprintf(debugFP, "FEN castling rights:");
17429         for(i=0; i<nrCastlingRights; i++)
17430         fprintf(debugFP, " %d", board[CASTLING][i]);
17431         fprintf(debugFP, "\n");
17432     }
17433
17434       while(*p==' ') p++;
17435     }
17436
17437     /* read e.p. field in games that know e.p. capture */
17438     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17439        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17440       if(*p=='-') {
17441         p++; board[EP_STATUS] = EP_NONE;
17442       } else {
17443          char c = *p++ - AAA;
17444
17445          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17446          if(*p >= '0' && *p <='9') p++;
17447          board[EP_STATUS] = c;
17448       }
17449     }
17450
17451
17452     if(sscanf(p, "%d", &i) == 1) {
17453         FENrulePlies = i; /* 50-move ply counter */
17454         /* (The move number is still ignored)    */
17455     }
17456
17457     return TRUE;
17458 }
17459
17460 void
17461 EditPositionPasteFEN (char *fen)
17462 {
17463   if (fen != NULL) {
17464     Board initial_position;
17465
17466     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17467       DisplayError(_("Bad FEN position in clipboard"), 0);
17468       return ;
17469     } else {
17470       int savedBlackPlaysFirst = blackPlaysFirst;
17471       EditPositionEvent();
17472       blackPlaysFirst = savedBlackPlaysFirst;
17473       CopyBoard(boards[0], initial_position);
17474       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17475       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17476       DisplayBothClocks();
17477       DrawPosition(FALSE, boards[currentMove]);
17478     }
17479   }
17480 }
17481
17482 static char cseq[12] = "\\   ";
17483
17484 Boolean
17485 set_cont_sequence (char *new_seq)
17486 {
17487     int len;
17488     Boolean ret;
17489
17490     // handle bad attempts to set the sequence
17491         if (!new_seq)
17492                 return 0; // acceptable error - no debug
17493
17494     len = strlen(new_seq);
17495     ret = (len > 0) && (len < sizeof(cseq));
17496     if (ret)
17497       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17498     else if (appData.debugMode)
17499       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17500     return ret;
17501 }
17502
17503 /*
17504     reformat a source message so words don't cross the width boundary.  internal
17505     newlines are not removed.  returns the wrapped size (no null character unless
17506     included in source message).  If dest is NULL, only calculate the size required
17507     for the dest buffer.  lp argument indicats line position upon entry, and it's
17508     passed back upon exit.
17509 */
17510 int
17511 wrap (char *dest, char *src, int count, int width, int *lp)
17512 {
17513     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17514
17515     cseq_len = strlen(cseq);
17516     old_line = line = *lp;
17517     ansi = len = clen = 0;
17518
17519     for (i=0; i < count; i++)
17520     {
17521         if (src[i] == '\033')
17522             ansi = 1;
17523
17524         // if we hit the width, back up
17525         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17526         {
17527             // store i & len in case the word is too long
17528             old_i = i, old_len = len;
17529
17530             // find the end of the last word
17531             while (i && src[i] != ' ' && src[i] != '\n')
17532             {
17533                 i--;
17534                 len--;
17535             }
17536
17537             // word too long?  restore i & len before splitting it
17538             if ((old_i-i+clen) >= width)
17539             {
17540                 i = old_i;
17541                 len = old_len;
17542             }
17543
17544             // extra space?
17545             if (i && src[i-1] == ' ')
17546                 len--;
17547
17548             if (src[i] != ' ' && src[i] != '\n')
17549             {
17550                 i--;
17551                 if (len)
17552                     len--;
17553             }
17554
17555             // now append the newline and continuation sequence
17556             if (dest)
17557                 dest[len] = '\n';
17558             len++;
17559             if (dest)
17560                 strncpy(dest+len, cseq, cseq_len);
17561             len += cseq_len;
17562             line = cseq_len;
17563             clen = cseq_len;
17564             continue;
17565         }
17566
17567         if (dest)
17568             dest[len] = src[i];
17569         len++;
17570         if (!ansi)
17571             line++;
17572         if (src[i] == '\n')
17573             line = 0;
17574         if (src[i] == 'm')
17575             ansi = 0;
17576     }
17577     if (dest && appData.debugMode)
17578     {
17579         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17580             count, width, line, len, *lp);
17581         show_bytes(debugFP, src, count);
17582         fprintf(debugFP, "\ndest: ");
17583         show_bytes(debugFP, dest, len);
17584         fprintf(debugFP, "\n");
17585     }
17586     *lp = dest ? line : old_line;
17587
17588     return len;
17589 }
17590
17591 // [HGM] vari: routines for shelving variations
17592 Boolean modeRestore = FALSE;
17593
17594 void
17595 PushInner (int firstMove, int lastMove)
17596 {
17597         int i, j, nrMoves = lastMove - firstMove;
17598
17599         // push current tail of game on stack
17600         savedResult[storedGames] = gameInfo.result;
17601         savedDetails[storedGames] = gameInfo.resultDetails;
17602         gameInfo.resultDetails = NULL;
17603         savedFirst[storedGames] = firstMove;
17604         savedLast [storedGames] = lastMove;
17605         savedFramePtr[storedGames] = framePtr;
17606         framePtr -= nrMoves; // reserve space for the boards
17607         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17608             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17609             for(j=0; j<MOVE_LEN; j++)
17610                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17611             for(j=0; j<2*MOVE_LEN; j++)
17612                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17613             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17614             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17615             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17616             pvInfoList[firstMove+i-1].depth = 0;
17617             commentList[framePtr+i] = commentList[firstMove+i];
17618             commentList[firstMove+i] = NULL;
17619         }
17620
17621         storedGames++;
17622         forwardMostMove = firstMove; // truncate game so we can start variation
17623 }
17624
17625 void
17626 PushTail (int firstMove, int lastMove)
17627 {
17628         if(appData.icsActive) { // only in local mode
17629                 forwardMostMove = currentMove; // mimic old ICS behavior
17630                 return;
17631         }
17632         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17633
17634         PushInner(firstMove, lastMove);
17635         if(storedGames == 1) GreyRevert(FALSE);
17636         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17637 }
17638
17639 void
17640 PopInner (Boolean annotate)
17641 {
17642         int i, j, nrMoves;
17643         char buf[8000], moveBuf[20];
17644
17645         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17646         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17647         nrMoves = savedLast[storedGames] - currentMove;
17648         if(annotate) {
17649                 int cnt = 10;
17650                 if(!WhiteOnMove(currentMove))
17651                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17652                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17653                 for(i=currentMove; i<forwardMostMove; i++) {
17654                         if(WhiteOnMove(i))
17655                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17656                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17657                         strcat(buf, moveBuf);
17658                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17659                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17660                 }
17661                 strcat(buf, ")");
17662         }
17663         for(i=1; i<=nrMoves; i++) { // copy last variation back
17664             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17665             for(j=0; j<MOVE_LEN; j++)
17666                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17667             for(j=0; j<2*MOVE_LEN; j++)
17668                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17669             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17670             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17671             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17672             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17673             commentList[currentMove+i] = commentList[framePtr+i];
17674             commentList[framePtr+i] = NULL;
17675         }
17676         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17677         framePtr = savedFramePtr[storedGames];
17678         gameInfo.result = savedResult[storedGames];
17679         if(gameInfo.resultDetails != NULL) {
17680             free(gameInfo.resultDetails);
17681       }
17682         gameInfo.resultDetails = savedDetails[storedGames];
17683         forwardMostMove = currentMove + nrMoves;
17684 }
17685
17686 Boolean
17687 PopTail (Boolean annotate)
17688 {
17689         if(appData.icsActive) return FALSE; // only in local mode
17690         if(!storedGames) return FALSE; // sanity
17691         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17692
17693         PopInner(annotate);
17694         if(currentMove < forwardMostMove) ForwardEvent(); else
17695         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17696
17697         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17698         return TRUE;
17699 }
17700
17701 void
17702 CleanupTail ()
17703 {       // remove all shelved variations
17704         int i;
17705         for(i=0; i<storedGames; i++) {
17706             if(savedDetails[i])
17707                 free(savedDetails[i]);
17708             savedDetails[i] = NULL;
17709         }
17710         for(i=framePtr; i<MAX_MOVES; i++) {
17711                 if(commentList[i]) free(commentList[i]);
17712                 commentList[i] = NULL;
17713         }
17714         framePtr = MAX_MOVES-1;
17715         storedGames = 0;
17716 }
17717
17718 void
17719 LoadVariation (int index, char *text)
17720 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17721         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17722         int level = 0, move;
17723
17724         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17725         // first find outermost bracketing variation
17726         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17727             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17728                 if(*p == '{') wait = '}'; else
17729                 if(*p == '[') wait = ']'; else
17730                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17731                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17732             }
17733             if(*p == wait) wait = NULLCHAR; // closing ]} found
17734             p++;
17735         }
17736         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17737         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17738         end[1] = NULLCHAR; // clip off comment beyond variation
17739         ToNrEvent(currentMove-1);
17740         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17741         // kludge: use ParsePV() to append variation to game
17742         move = currentMove;
17743         ParsePV(start, TRUE, TRUE);
17744         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17745         ClearPremoveHighlights();
17746         CommentPopDown();
17747         ToNrEvent(currentMove+1);
17748 }
17749
17750 void
17751 LoadTheme ()
17752 {
17753     char *p, *q, buf[MSG_SIZ];
17754     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17755         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17756         ParseArgsFromString(buf);
17757         ActivateTheme(TRUE); // also redo colors
17758         return;
17759     }
17760     p = nickName;
17761     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17762     {
17763         int len;
17764         q = appData.themeNames;
17765         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17766       if(appData.useBitmaps) {
17767         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17768                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17769                 appData.liteBackTextureMode,
17770                 appData.darkBackTextureMode );
17771       } else {
17772         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17773                 Col2Text(2),   // lightSquareColor
17774                 Col2Text(3) ); // darkSquareColor
17775       }
17776       if(appData.useBorder) {
17777         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17778                 appData.border);
17779       } else {
17780         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17781       }
17782       if(appData.useFont) {
17783         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17784                 appData.renderPiecesWithFont,
17785                 appData.fontToPieceTable,
17786                 Col2Text(9),    // appData.fontBackColorWhite
17787                 Col2Text(10) ); // appData.fontForeColorBlack
17788       } else {
17789         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17790                 appData.pieceDirectory);
17791         if(!appData.pieceDirectory[0])
17792           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17793                 Col2Text(0),   // whitePieceColor
17794                 Col2Text(1) ); // blackPieceColor
17795       }
17796       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17797                 Col2Text(4),   // highlightSquareColor
17798                 Col2Text(5) ); // premoveHighlightColor
17799         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17800         if(insert != q) insert[-1] = NULLCHAR;
17801         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17802         if(q)   free(q);
17803     }
17804     ActivateTheme(FALSE);
17805 }