configure: enable silent rules by default
[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 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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy( char *dst, const char *src, size_t count )
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble(u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags(index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
527 };
528
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
534 };
535
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
541 };
542
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackMan, BlackFerz,
547         BlackKing, BlackMan, BlackKnight, BlackRook }
548 };
549
550
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
557 };
558
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
564 };
565
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
571 };
572
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
578 };
579
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
585 };
586
587 #ifdef GOTHIC
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
593 };
594 #else // !GOTHIC
595 #define GothicArray CapablancaArray
596 #endif // !GOTHIC
597
598 #ifdef FALCON
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !FALCON
606 #define FalconArray CapablancaArray
607 #endif // !FALCON
608
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
615
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 };
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
626
627
628 Board initialPosition;
629
630
631 /* Convert str to a rating. Checks for special cases of "----",
632
633    "++++", etc. Also strips ()'s */
634 int
635 string_to_rating(str)
636   char *str;
637 {
638   while(*str && !isdigit(*str)) ++str;
639   if (!*str)
640     return 0;   /* One of the special "no rating" cases */
641   else
642     return atoi(str);
643 }
644
645 void
646 ClearProgramStats()
647 {
648     /* Init programStats */
649     programStats.movelist[0] = 0;
650     programStats.depth = 0;
651     programStats.nr_moves = 0;
652     programStats.moves_left = 0;
653     programStats.nodes = 0;
654     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
655     programStats.score = 0;
656     programStats.got_only_move = 0;
657     programStats.got_fail = 0;
658     programStats.line_is_book = 0;
659 }
660
661 void
662 InitBackEnd1()
663 {
664     int matched, min, sec;
665
666     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
667     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
668
669     GetTimeMark(&programStartTime);
670     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
671
672     ClearProgramStats();
673     programStats.ok_to_send = 1;
674     programStats.seen_stat = 0;
675
676     /*
677      * Initialize game list
678      */
679     ListNew(&gameList);
680
681
682     /*
683      * Internet chess server status
684      */
685     if (appData.icsActive) {
686         appData.matchMode = FALSE;
687         appData.matchGames = 0;
688 #if ZIPPY
689         appData.noChessProgram = !appData.zippyPlay;
690 #else
691         appData.zippyPlay = FALSE;
692         appData.zippyTalk = FALSE;
693         appData.noChessProgram = TRUE;
694 #endif
695         if (*appData.icsHelper != NULLCHAR) {
696             appData.useTelnet = TRUE;
697             appData.telnetProgram = appData.icsHelper;
698         }
699     } else {
700         appData.zippyTalk = appData.zippyPlay = FALSE;
701     }
702
703     /* [AS] Initialize pv info list [HGM] and game state */
704     {
705         int i, j;
706
707         for( i=0; i<=framePtr; i++ ) {
708             pvInfoList[i].depth = -1;
709             boards[i][EP_STATUS] = EP_NONE;
710             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
711         }
712     }
713
714     /*
715      * Parse timeControl resource
716      */
717     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
718                           appData.movesPerSession)) {
719         char buf[MSG_SIZ];
720         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
721         DisplayFatalError(buf, 0, 2);
722     }
723
724     /*
725      * Parse searchTime resource
726      */
727     if (*appData.searchTime != NULLCHAR) {
728         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
729         if (matched == 1) {
730             searchTime = min * 60;
731         } else if (matched == 2) {
732             searchTime = min * 60 + sec;
733         } else {
734             char buf[MSG_SIZ];
735             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
736             DisplayFatalError(buf, 0, 2);
737         }
738     }
739
740     /* [AS] Adjudication threshold */
741     adjudicateLossThreshold = appData.adjudicateLossThreshold;
742
743     first.which = "first";
744     second.which = "second";
745     first.maybeThinking = second.maybeThinking = FALSE;
746     first.pr = second.pr = NoProc;
747     first.isr = second.isr = NULL;
748     first.sendTime = second.sendTime = 2;
749     first.sendDrawOffers = 1;
750     if (appData.firstPlaysBlack) {
751         first.twoMachinesColor = "black\n";
752         second.twoMachinesColor = "white\n";
753     } else {
754         first.twoMachinesColor = "white\n";
755         second.twoMachinesColor = "black\n";
756     }
757     first.program = appData.firstChessProgram;
758     second.program = appData.secondChessProgram;
759     first.host = appData.firstHost;
760     second.host = appData.secondHost;
761     first.dir = appData.firstDirectory;
762     second.dir = appData.secondDirectory;
763     first.other = &second;
764     second.other = &first;
765     first.initString = appData.initString;
766     second.initString = appData.secondInitString;
767     first.computerString = appData.firstComputerString;
768     second.computerString = appData.secondComputerString;
769     first.useSigint = second.useSigint = TRUE;
770     first.useSigterm = second.useSigterm = TRUE;
771     first.reuse = appData.reuseFirst;
772     second.reuse = appData.reuseSecond;
773     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
774     second.nps = appData.secondNPS;
775     first.useSetboard = second.useSetboard = FALSE;
776     first.useSAN = second.useSAN = FALSE;
777     first.usePing = second.usePing = FALSE;
778     first.lastPing = second.lastPing = 0;
779     first.lastPong = second.lastPong = 0;
780     first.usePlayother = second.usePlayother = FALSE;
781     first.useColors = second.useColors = TRUE;
782     first.useUsermove = second.useUsermove = FALSE;
783     first.sendICS = second.sendICS = FALSE;
784     first.sendName = second.sendName = appData.icsActive;
785     first.sdKludge = second.sdKludge = FALSE;
786     first.stKludge = second.stKludge = FALSE;
787     TidyProgramName(first.program, first.host, first.tidy);
788     TidyProgramName(second.program, second.host, second.tidy);
789     first.matchWins = second.matchWins = 0;
790     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
791     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
792     first.analysisSupport = second.analysisSupport = 2; /* detect */
793     first.analyzing = second.analyzing = FALSE;
794     first.initDone = second.initDone = FALSE;
795
796     /* New features added by Tord: */
797     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
798     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
799     /* End of new features added by Tord. */
800     first.fenOverride  = appData.fenOverride1;
801     second.fenOverride = appData.fenOverride2;
802
803     /* [HGM] time odds: set factor for each machine */
804     first.timeOdds  = appData.firstTimeOdds;
805     second.timeOdds = appData.secondTimeOdds;
806     { float norm = 1;
807         if(appData.timeOddsMode) {
808             norm = first.timeOdds;
809             if(norm > second.timeOdds) norm = second.timeOdds;
810         }
811         first.timeOdds /= norm;
812         second.timeOdds /= norm;
813     }
814
815     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
816     first.accumulateTC = appData.firstAccumulateTC;
817     second.accumulateTC = appData.secondAccumulateTC;
818     first.maxNrOfSessions = second.maxNrOfSessions = 1;
819
820     /* [HGM] debug */
821     first.debug = second.debug = FALSE;
822     first.supportsNPS = second.supportsNPS = UNKNOWN;
823
824     /* [HGM] options */
825     first.optionSettings  = appData.firstOptions;
826     second.optionSettings = appData.secondOptions;
827
828     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
829     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
830     first.isUCI = appData.firstIsUCI; /* [AS] */
831     second.isUCI = appData.secondIsUCI; /* [AS] */
832     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
833     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
834
835     if (appData.firstProtocolVersion > PROTOVER
836         || appData.firstProtocolVersion < 1)
837       {
838         char buf[MSG_SIZ];
839         int len;
840
841         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
842                        appData.firstProtocolVersion);
843         if( (len > MSG_SIZ) && appData.debugMode )
844           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
845
846         DisplayFatalError(buf, 0, 2);
847       }
848     else
849       {
850         first.protocolVersion = appData.firstProtocolVersion;
851       }
852
853     if (appData.secondProtocolVersion > PROTOVER
854         || appData.secondProtocolVersion < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.secondProtocolVersion);
861         if( (len > MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         second.protocolVersion = appData.secondProtocolVersion;
869       }
870
871     if (appData.icsActive) {
872         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
873 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
874     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
875         appData.clockMode = FALSE;
876         first.sendTime = second.sendTime = 0;
877     }
878
879 #if ZIPPY
880     /* Override some settings from environment variables, for backward
881        compatibility.  Unfortunately it's not feasible to have the env
882        vars just set defaults, at least in xboard.  Ugh.
883     */
884     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
885       ZippyInit();
886     }
887 #endif
888
889     if (appData.noChessProgram) {
890         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
891         sprintf(programVersion, "%s", PACKAGE_STRING);
892     } else {
893       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
894       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
895       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
896     }
897
898     if (!appData.icsActive) {
899       char buf[MSG_SIZ];
900       int len;
901
902       /* Check for variants that are supported only in ICS mode,
903          or not at all.  Some that are accepted here nevertheless
904          have bugs; see comments below.
905       */
906       VariantClass variant = StringToVariant(appData.variant);
907       switch (variant) {
908       case VariantBughouse:     /* need four players and two boards */
909       case VariantKriegspiel:   /* need to hide pieces and move details */
910         /* case VariantFischeRandom: (Fabien: moved below) */
911         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
912         if( (len > MSG_SIZ) && appData.debugMode )
913           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
914
915         DisplayFatalError(buf, 0, 2);
916         return;
917
918       case VariantUnknown:
919       case VariantLoadable:
920       case Variant29:
921       case Variant30:
922       case Variant31:
923       case Variant32:
924       case Variant33:
925       case Variant34:
926       case Variant35:
927       case Variant36:
928       default:
929         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
930         if( (len > MSG_SIZ) && appData.debugMode )
931           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
932
933         DisplayFatalError(buf, 0, 2);
934         return;
935
936       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
937       case VariantFairy:      /* [HGM] TestLegality definitely off! */
938       case VariantGothic:     /* [HGM] should work */
939       case VariantCapablanca: /* [HGM] should work */
940       case VariantCourier:    /* [HGM] initial forced moves not implemented */
941       case VariantShogi:      /* [HGM] could still mate with pawn drop */
942       case VariantKnightmate: /* [HGM] should work */
943       case VariantCylinder:   /* [HGM] untested */
944       case VariantFalcon:     /* [HGM] untested */
945       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
946                                  offboard interposition not understood */
947       case VariantNormal:     /* definitely works! */
948       case VariantWildCastle: /* pieces not automatically shuffled */
949       case VariantNoCastle:   /* pieces not automatically shuffled */
950       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
951       case VariantLosers:     /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantSuicide:    /* should work except for win condition,
954                                  and doesn't know captures are mandatory */
955       case VariantGiveaway:   /* should work except for win condition,
956                                  and doesn't know captures are mandatory */
957       case VariantTwoKings:   /* should work */
958       case VariantAtomic:     /* should work except for win condition */
959       case Variant3Check:     /* should work except for win condition */
960       case VariantShatranj:   /* should work except for all win conditions */
961       case VariantMakruk:     /* should work except for daw countdown */
962       case VariantBerolina:   /* might work if TestLegality is off */
963       case VariantCapaRandom: /* should work */
964       case VariantJanus:      /* should work */
965       case VariantSuper:      /* experimental */
966       case VariantGreat:      /* experimental, requires legality testing to be off */
967       case VariantSChess:     /* S-Chess, should work */
968       case VariantSpartan:    /* should work */
969         break;
970       }
971     }
972
973     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
974     InitEngineUCI( installDir, &second );
975 }
976
977 int NextIntegerFromString( char ** str, long * value )
978 {
979     int result = -1;
980     char * s = *str;
981
982     while( *s == ' ' || *s == '\t' ) {
983         s++;
984     }
985
986     *value = 0;
987
988     if( *s >= '0' && *s <= '9' ) {
989         while( *s >= '0' && *s <= '9' ) {
990             *value = *value * 10 + (*s - '0');
991             s++;
992         }
993
994         result = 0;
995     }
996
997     *str = s;
998
999     return result;
1000 }
1001
1002 int NextTimeControlFromString( char ** str, long * value )
1003 {
1004     long temp;
1005     int result = NextIntegerFromString( str, &temp );
1006
1007     if( result == 0 ) {
1008         *value = temp * 60; /* Minutes */
1009         if( **str == ':' ) {
1010             (*str)++;
1011             result = NextIntegerFromString( str, &temp );
1012             *value += temp; /* Seconds */
1013         }
1014     }
1015
1016     return result;
1017 }
1018
1019 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1020 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1021     int result = -1, type = 0; long temp, temp2;
1022
1023     if(**str != ':') return -1; // old params remain in force!
1024     (*str)++;
1025     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1026     if( NextIntegerFromString( str, &temp ) ) return -1;
1027     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1028
1029     if(**str != '/') {
1030         /* time only: incremental or sudden-death time control */
1031         if(**str == '+') { /* increment follows; read it */
1032             (*str)++;
1033             if(**str == '!') type = *(*str)++; // Bronstein TC
1034             if(result = NextIntegerFromString( str, &temp2)) return -1;
1035             *inc = temp2 * 1000;
1036             if(**str == '.') { // read fraction of increment
1037                 char *start = ++(*str);
1038                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1039                 temp2 *= 1000;
1040                 while(start++ < *str) temp2 /= 10;
1041                 *inc += temp2;
1042             }
1043         } else *inc = 0;
1044         *moves = 0; *tc = temp * 1000; *incType = type;
1045         return 0;
1046     }
1047
1048     (*str)++; /* classical time control */
1049     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1050
1051     if(result == 0) {
1052         *moves = temp;
1053         *tc    = temp2 * 1000;
1054         *inc   = 0;
1055         *incType = type;
1056     }
1057     return result;
1058 }
1059
1060 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1061 {   /* [HGM] get time to add from the multi-session time-control string */
1062     int incType, moves=1; /* kludge to force reading of first session */
1063     long time, increment;
1064     char *s = tcString;
1065
1066     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1067     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1068     do {
1069         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1070         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1071         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1072         if(movenr == -1) return time;    /* last move before new session     */
1073         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1074         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1075         if(!moves) return increment;     /* current session is incremental   */
1076         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1077     } while(movenr >= -1);               /* try again for next session       */
1078
1079     return 0; // no new time quota on this move
1080 }
1081
1082 int
1083 ParseTimeControl(tc, ti, mps)
1084      char *tc;
1085      float ti;
1086      int mps;
1087 {
1088   long tc1;
1089   long tc2;
1090   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1091   int min, sec=0;
1092
1093   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1094   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1095       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1096   if(ti > 0) {
1097
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1100     else 
1101       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1102   } else {
1103     if(mps)
1104       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1105     else 
1106       snprintf(buf, MSG_SIZ, ":%s", mytc);
1107   }
1108   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1109   
1110   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1111     return FALSE;
1112   }
1113
1114   if( *tc == '/' ) {
1115     /* Parse second time control */
1116     tc++;
1117
1118     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1119       return FALSE;
1120     }
1121
1122     if( tc2 == 0 ) {
1123       return FALSE;
1124     }
1125
1126     timeControl_2 = tc2 * 1000;
1127   }
1128   else {
1129     timeControl_2 = 0;
1130   }
1131
1132   if( tc1 == 0 ) {
1133     return FALSE;
1134   }
1135
1136   timeControl = tc1 * 1000;
1137
1138   if (ti >= 0) {
1139     timeIncrement = ti * 1000;  /* convert to ms */
1140     movesPerSession = 0;
1141   } else {
1142     timeIncrement = 0;
1143     movesPerSession = mps;
1144   }
1145   return TRUE;
1146 }
1147
1148 void
1149 InitBackEnd2()
1150 {
1151     if (appData.debugMode) {
1152         fprintf(debugFP, "%s\n", programVersion);
1153     }
1154
1155     set_cont_sequence(appData.wrapContSeq);
1156     if (appData.matchGames > 0) {
1157         appData.matchMode = TRUE;
1158     } else if (appData.matchMode) {
1159         appData.matchGames = 1;
1160     }
1161     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1162         appData.matchGames = appData.sameColorGames;
1163     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1164         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1165         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1166     }
1167     Reset(TRUE, FALSE);
1168     if (appData.noChessProgram || first.protocolVersion == 1) {
1169       InitBackEnd3();
1170     } else {
1171       /* kludge: allow timeout for initial "feature" commands */
1172       FreezeUI();
1173       DisplayMessage("", _("Starting chess program"));
1174       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1175     }
1176 }
1177
1178 void
1179 MatchEvent(int mode)
1180 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = mode;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1209         TwoMachinesEvent();
1210 }
1211
1212 void
1213 InitBackEnd3 P((void))
1214 {
1215     GameMode initialMode;
1216     char buf[MSG_SIZ];
1217     int err, len;
1218
1219     InitChessProgram(&first, startedFromSetupPosition);
1220
1221     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1222         free(programVersion);
1223         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1224         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1225     }
1226
1227     if (appData.icsActive) {
1228 #ifdef WIN32
1229         /* [DM] Make a console window if needed [HGM] merged ifs */
1230         ConsoleCreate();
1231 #endif
1232         err = establish();
1233         if (err != 0)
1234           {
1235             if (*appData.icsCommPort != NULLCHAR)
1236               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1237                              appData.icsCommPort);
1238             else
1239               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1240                         appData.icsHost, appData.icsPort);
1241
1242             if( (len > MSG_SIZ) && appData.debugMode )
1243               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1244
1245             DisplayFatalError(buf, err, 1);
1246             return;
1247         }
1248         SetICSMode();
1249         telnetISR =
1250           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1251         fromUserISR =
1252           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1253         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1254             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1255     } else if (appData.noChessProgram) {
1256         SetNCPMode();
1257     } else {
1258         SetGNUMode();
1259     }
1260
1261     if (*appData.cmailGameName != NULLCHAR) {
1262         SetCmailMode();
1263         OpenLoopback(&cmailPR);
1264         cmailISR =
1265           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1266     }
1267
1268     ThawUI();
1269     DisplayMessage("", "");
1270     if (StrCaseCmp(appData.initialMode, "") == 0) {
1271       initialMode = BeginningOfGame;
1272     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1273       initialMode = TwoMachinesPlay;
1274     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1275       initialMode = AnalyzeFile;
1276     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1277       initialMode = AnalyzeMode;
1278     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1279       initialMode = MachinePlaysWhite;
1280     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1281       initialMode = MachinePlaysBlack;
1282     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1283       initialMode = EditGame;
1284     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1285       initialMode = EditPosition;
1286     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1287       initialMode = Training;
1288     } else {
1289       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1290       if( (len > MSG_SIZ) && appData.debugMode )
1291         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1292
1293       DisplayFatalError(buf, 0, 2);
1294       return;
1295     }
1296
1297     if (appData.matchMode) {
1298         MatchEvent(TRUE);
1299     } else if (*appData.cmailGameName != NULLCHAR) {
1300         /* Set up cmail mode */
1301         ReloadCmailMsgEvent(TRUE);
1302     } else {
1303         /* Set up other modes */
1304         if (initialMode == AnalyzeFile) {
1305           if (*appData.loadGameFile == NULLCHAR) {
1306             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1307             return;
1308           }
1309         }
1310         if (*appData.loadGameFile != NULLCHAR) {
1311             (void) LoadGameFromFile(appData.loadGameFile,
1312                                     appData.loadGameIndex,
1313                                     appData.loadGameFile, TRUE);
1314         } else if (*appData.loadPositionFile != NULLCHAR) {
1315             (void) LoadPositionFromFile(appData.loadPositionFile,
1316                                         appData.loadPositionIndex,
1317                                         appData.loadPositionFile);
1318             /* [HGM] try to make self-starting even after FEN load */
1319             /* to allow automatic setup of fairy variants with wtm */
1320             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1321                 gameMode = BeginningOfGame;
1322                 setboardSpoiledMachineBlack = 1;
1323             }
1324             /* [HGM] loadPos: make that every new game uses the setup */
1325             /* from file as long as we do not switch variant          */
1326             if(!blackPlaysFirst) {
1327                 startedFromPositionFile = TRUE;
1328                 CopyBoard(filePosition, boards[0]);
1329             }
1330         }
1331         if (initialMode == AnalyzeMode) {
1332           if (appData.noChessProgram) {
1333             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1334             return;
1335           }
1336           if (appData.icsActive) {
1337             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1338             return;
1339           }
1340           AnalyzeModeEvent();
1341         } else if (initialMode == AnalyzeFile) {
1342           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1343           ShowThinkingEvent();
1344           AnalyzeFileEvent();
1345           AnalysisPeriodicEvent(1);
1346         } else if (initialMode == MachinePlaysWhite) {
1347           if (appData.noChessProgram) {
1348             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1349                               0, 2);
1350             return;
1351           }
1352           if (appData.icsActive) {
1353             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1354                               0, 2);
1355             return;
1356           }
1357           MachineWhiteEvent();
1358         } else if (initialMode == MachinePlaysBlack) {
1359           if (appData.noChessProgram) {
1360             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1361                               0, 2);
1362             return;
1363           }
1364           if (appData.icsActive) {
1365             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1366                               0, 2);
1367             return;
1368           }
1369           MachineBlackEvent();
1370         } else if (initialMode == TwoMachinesPlay) {
1371           if (appData.noChessProgram) {
1372             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1373                               0, 2);
1374             return;
1375           }
1376           if (appData.icsActive) {
1377             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1378                               0, 2);
1379             return;
1380           }
1381           TwoMachinesEvent();
1382         } else if (initialMode == EditGame) {
1383           EditGameEvent();
1384         } else if (initialMode == EditPosition) {
1385           EditPositionEvent();
1386         } else if (initialMode == Training) {
1387           if (*appData.loadGameFile == NULLCHAR) {
1388             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1389             return;
1390           }
1391           TrainingEvent();
1392         }
1393     }
1394 }
1395
1396 /*
1397  * Establish will establish a contact to a remote host.port.
1398  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1399  *  used to talk to the host.
1400  * Returns 0 if okay, error code if not.
1401  */
1402 int
1403 establish()
1404 {
1405     char buf[MSG_SIZ];
1406
1407     if (*appData.icsCommPort != NULLCHAR) {
1408         /* Talk to the host through a serial comm port */
1409         return OpenCommPort(appData.icsCommPort, &icsPR);
1410
1411     } else if (*appData.gateway != NULLCHAR) {
1412         if (*appData.remoteShell == NULLCHAR) {
1413             /* Use the rcmd protocol to run telnet program on a gateway host */
1414             snprintf(buf, sizeof(buf), "%s %s %s",
1415                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1416             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1417
1418         } else {
1419             /* Use the rsh program to run telnet program on a gateway host */
1420             if (*appData.remoteUser == NULLCHAR) {
1421                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1422                         appData.gateway, appData.telnetProgram,
1423                         appData.icsHost, appData.icsPort);
1424             } else {
1425                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1426                         appData.remoteShell, appData.gateway,
1427                         appData.remoteUser, appData.telnetProgram,
1428                         appData.icsHost, appData.icsPort);
1429             }
1430             return StartChildProcess(buf, "", &icsPR);
1431
1432         }
1433     } else if (appData.useTelnet) {
1434         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1435
1436     } else {
1437         /* TCP socket interface differs somewhat between
1438            Unix and NT; handle details in the front end.
1439            */
1440         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1441     }
1442 }
1443
1444 void EscapeExpand(char *p, char *q)
1445 {       // [HGM] initstring: routine to shape up string arguments
1446         while(*p++ = *q++) if(p[-1] == '\\')
1447             switch(*q++) {
1448                 case 'n': p[-1] = '\n'; break;
1449                 case 'r': p[-1] = '\r'; break;
1450                 case 't': p[-1] = '\t'; break;
1451                 case '\\': p[-1] = '\\'; break;
1452                 case 0: *p = 0; return;
1453                 default: p[-1] = q[-1]; break;
1454             }
1455 }
1456
1457 void
1458 show_bytes(fp, buf, count)
1459      FILE *fp;
1460      char *buf;
1461      int count;
1462 {
1463     while (count--) {
1464         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1465             fprintf(fp, "\\%03o", *buf & 0xff);
1466         } else {
1467             putc(*buf, fp);
1468         }
1469         buf++;
1470     }
1471     fflush(fp);
1472 }
1473
1474 /* Returns an errno value */
1475 int
1476 OutputMaybeTelnet(pr, message, count, outError)
1477      ProcRef pr;
1478      char *message;
1479      int count;
1480      int *outError;
1481 {
1482     char buf[8192], *p, *q, *buflim;
1483     int left, newcount, outcount;
1484
1485     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1486         *appData.gateway != NULLCHAR) {
1487         if (appData.debugMode) {
1488             fprintf(debugFP, ">ICS: ");
1489             show_bytes(debugFP, message, count);
1490             fprintf(debugFP, "\n");
1491         }
1492         return OutputToProcess(pr, message, count, outError);
1493     }
1494
1495     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1496     p = message;
1497     q = buf;
1498     left = count;
1499     newcount = 0;
1500     while (left) {
1501         if (q >= buflim) {
1502             if (appData.debugMode) {
1503                 fprintf(debugFP, ">ICS: ");
1504                 show_bytes(debugFP, buf, newcount);
1505                 fprintf(debugFP, "\n");
1506             }
1507             outcount = OutputToProcess(pr, buf, newcount, outError);
1508             if (outcount < newcount) return -1; /* to be sure */
1509             q = buf;
1510             newcount = 0;
1511         }
1512         if (*p == '\n') {
1513             *q++ = '\r';
1514             newcount++;
1515         } else if (((unsigned char) *p) == TN_IAC) {
1516             *q++ = (char) TN_IAC;
1517             newcount ++;
1518         }
1519         *q++ = *p++;
1520         newcount++;
1521         left--;
1522     }
1523     if (appData.debugMode) {
1524         fprintf(debugFP, ">ICS: ");
1525         show_bytes(debugFP, buf, newcount);
1526         fprintf(debugFP, "\n");
1527     }
1528     outcount = OutputToProcess(pr, buf, newcount, outError);
1529     if (outcount < newcount) return -1; /* to be sure */
1530     return count;
1531 }
1532
1533 void
1534 read_from_player(isr, closure, message, count, error)
1535      InputSourceRef isr;
1536      VOIDSTAR closure;
1537      char *message;
1538      int count;
1539      int error;
1540 {
1541     int outError, outCount;
1542     static int gotEof = 0;
1543
1544     /* Pass data read from player on to ICS */
1545     if (count > 0) {
1546         gotEof = 0;
1547         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1548         if (outCount < count) {
1549             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1550         }
1551     } else if (count < 0) {
1552         RemoveInputSource(isr);
1553         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1554     } else if (gotEof++ > 0) {
1555         RemoveInputSource(isr);
1556         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1557     }
1558 }
1559
1560 void
1561 KeepAlive()
1562 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1563     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1564     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1565     SendToICS("date\n");
1566     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567 }
1568
1569 /* added routine for printf style output to ics */
1570 void ics_printf(char *format, ...)
1571 {
1572     char buffer[MSG_SIZ];
1573     va_list args;
1574
1575     va_start(args, format);
1576     vsnprintf(buffer, sizeof(buffer), format, args);
1577     buffer[sizeof(buffer)-1] = '\0';
1578     SendToICS(buffer);
1579     va_end(args);
1580 }
1581
1582 void
1583 SendToICS(s)
1584      char *s;
1585 {
1586     int count, outCount, outError;
1587
1588     if (icsPR == NULL) return;
1589
1590     count = strlen(s);
1591     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1592     if (outCount < count) {
1593         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1594     }
1595 }
1596
1597 /* This is used for sending logon scripts to the ICS. Sending
1598    without a delay causes problems when using timestamp on ICC
1599    (at least on my machine). */
1600 void
1601 SendToICSDelayed(s,msdelay)
1602      char *s;
1603      long msdelay;
1604 {
1605     int count, outCount, outError;
1606
1607     if (icsPR == NULL) return;
1608
1609     count = strlen(s);
1610     if (appData.debugMode) {
1611         fprintf(debugFP, ">ICS: ");
1612         show_bytes(debugFP, s, count);
1613         fprintf(debugFP, "\n");
1614     }
1615     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1616                                       msdelay);
1617     if (outCount < count) {
1618         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1619     }
1620 }
1621
1622
1623 /* Remove all highlighting escape sequences in s
1624    Also deletes any suffix starting with '('
1625    */
1626 char *
1627 StripHighlightAndTitle(s)
1628      char *s;
1629 {
1630     static char retbuf[MSG_SIZ];
1631     char *p = retbuf;
1632
1633     while (*s != NULLCHAR) {
1634         while (*s == '\033') {
1635             while (*s != NULLCHAR && !isalpha(*s)) s++;
1636             if (*s != NULLCHAR) s++;
1637         }
1638         while (*s != NULLCHAR && *s != '\033') {
1639             if (*s == '(' || *s == '[') {
1640                 *p = NULLCHAR;
1641                 return retbuf;
1642             }
1643             *p++ = *s++;
1644         }
1645     }
1646     *p = NULLCHAR;
1647     return retbuf;
1648 }
1649
1650 /* Remove all highlighting escape sequences in s */
1651 char *
1652 StripHighlight(s)
1653      char *s;
1654 {
1655     static char retbuf[MSG_SIZ];
1656     char *p = retbuf;
1657
1658     while (*s != NULLCHAR) {
1659         while (*s == '\033') {
1660             while (*s != NULLCHAR && !isalpha(*s)) s++;
1661             if (*s != NULLCHAR) s++;
1662         }
1663         while (*s != NULLCHAR && *s != '\033') {
1664             *p++ = *s++;
1665         }
1666     }
1667     *p = NULLCHAR;
1668     return retbuf;
1669 }
1670
1671 char *variantNames[] = VARIANT_NAMES;
1672 char *
1673 VariantName(v)
1674      VariantClass v;
1675 {
1676     return variantNames[v];
1677 }
1678
1679
1680 /* Identify a variant from the strings the chess servers use or the
1681    PGN Variant tag names we use. */
1682 VariantClass
1683 StringToVariant(e)
1684      char *e;
1685 {
1686     char *p;
1687     int wnum = -1;
1688     VariantClass v = VariantNormal;
1689     int i, found = FALSE;
1690     char buf[MSG_SIZ];
1691     int len;
1692
1693     if (!e) return v;
1694
1695     /* [HGM] skip over optional board-size prefixes */
1696     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1697         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1698         while( *e++ != '_');
1699     }
1700
1701     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1702         v = VariantNormal;
1703         found = TRUE;
1704     } else
1705     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1706       if (StrCaseStr(e, variantNames[i])) {
1707         v = (VariantClass) i;
1708         found = TRUE;
1709         break;
1710       }
1711     }
1712
1713     if (!found) {
1714       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1715           || StrCaseStr(e, "wild/fr")
1716           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1717         v = VariantFischeRandom;
1718       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1719                  (i = 1, p = StrCaseStr(e, "w"))) {
1720         p += i;
1721         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1722         if (isdigit(*p)) {
1723           wnum = atoi(p);
1724         } else {
1725           wnum = -1;
1726         }
1727         switch (wnum) {
1728         case 0: /* FICS only, actually */
1729         case 1:
1730           /* Castling legal even if K starts on d-file */
1731           v = VariantWildCastle;
1732           break;
1733         case 2:
1734         case 3:
1735         case 4:
1736           /* Castling illegal even if K & R happen to start in
1737              normal positions. */
1738           v = VariantNoCastle;
1739           break;
1740         case 5:
1741         case 7:
1742         case 8:
1743         case 10:
1744         case 11:
1745         case 12:
1746         case 13:
1747         case 14:
1748         case 15:
1749         case 18:
1750         case 19:
1751           /* Castling legal iff K & R start in normal positions */
1752           v = VariantNormal;
1753           break;
1754         case 6:
1755         case 20:
1756         case 21:
1757           /* Special wilds for position setup; unclear what to do here */
1758           v = VariantLoadable;
1759           break;
1760         case 9:
1761           /* Bizarre ICC game */
1762           v = VariantTwoKings;
1763           break;
1764         case 16:
1765           v = VariantKriegspiel;
1766           break;
1767         case 17:
1768           v = VariantLosers;
1769           break;
1770         case 22:
1771           v = VariantFischeRandom;
1772           break;
1773         case 23:
1774           v = VariantCrazyhouse;
1775           break;
1776         case 24:
1777           v = VariantBughouse;
1778           break;
1779         case 25:
1780           v = Variant3Check;
1781           break;
1782         case 26:
1783           /* Not quite the same as FICS suicide! */
1784           v = VariantGiveaway;
1785           break;
1786         case 27:
1787           v = VariantAtomic;
1788           break;
1789         case 28:
1790           v = VariantShatranj;
1791           break;
1792
1793         /* Temporary names for future ICC types.  The name *will* change in
1794            the next xboard/WinBoard release after ICC defines it. */
1795         case 29:
1796           v = Variant29;
1797           break;
1798         case 30:
1799           v = Variant30;
1800           break;
1801         case 31:
1802           v = Variant31;
1803           break;
1804         case 32:
1805           v = Variant32;
1806           break;
1807         case 33:
1808           v = Variant33;
1809           break;
1810         case 34:
1811           v = Variant34;
1812           break;
1813         case 35:
1814           v = Variant35;
1815           break;
1816         case 36:
1817           v = Variant36;
1818           break;
1819         case 37:
1820           v = VariantShogi;
1821           break;
1822         case 38:
1823           v = VariantXiangqi;
1824           break;
1825         case 39:
1826           v = VariantCourier;
1827           break;
1828         case 40:
1829           v = VariantGothic;
1830           break;
1831         case 41:
1832           v = VariantCapablanca;
1833           break;
1834         case 42:
1835           v = VariantKnightmate;
1836           break;
1837         case 43:
1838           v = VariantFairy;
1839           break;
1840         case 44:
1841           v = VariantCylinder;
1842           break;
1843         case 45:
1844           v = VariantFalcon;
1845           break;
1846         case 46:
1847           v = VariantCapaRandom;
1848           break;
1849         case 47:
1850           v = VariantBerolina;
1851           break;
1852         case 48:
1853           v = VariantJanus;
1854           break;
1855         case 49:
1856           v = VariantSuper;
1857           break;
1858         case 50:
1859           v = VariantGreat;
1860           break;
1861         case -1:
1862           /* Found "wild" or "w" in the string but no number;
1863              must assume it's normal chess. */
1864           v = VariantNormal;
1865           break;
1866         default:
1867           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1868           if( (len > MSG_SIZ) && appData.debugMode )
1869             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1870
1871           DisplayError(buf, 0);
1872           v = VariantUnknown;
1873           break;
1874         }
1875       }
1876     }
1877     if (appData.debugMode) {
1878       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1879               e, wnum, VariantName(v));
1880     }
1881     return v;
1882 }
1883
1884 static int leftover_start = 0, leftover_len = 0;
1885 char star_match[STAR_MATCH_N][MSG_SIZ];
1886
1887 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1888    advance *index beyond it, and set leftover_start to the new value of
1889    *index; else return FALSE.  If pattern contains the character '*', it
1890    matches any sequence of characters not containing '\r', '\n', or the
1891    character following the '*' (if any), and the matched sequence(s) are
1892    copied into star_match.
1893    */
1894 int
1895 looking_at(buf, index, pattern)
1896      char *buf;
1897      int *index;
1898      char *pattern;
1899 {
1900     char *bufp = &buf[*index], *patternp = pattern;
1901     int star_count = 0;
1902     char *matchp = star_match[0];
1903
1904     for (;;) {
1905         if (*patternp == NULLCHAR) {
1906             *index = leftover_start = bufp - buf;
1907             *matchp = NULLCHAR;
1908             return TRUE;
1909         }
1910         if (*bufp == NULLCHAR) return FALSE;
1911         if (*patternp == '*') {
1912             if (*bufp == *(patternp + 1)) {
1913                 *matchp = NULLCHAR;
1914                 matchp = star_match[++star_count];
1915                 patternp += 2;
1916                 bufp++;
1917                 continue;
1918             } else if (*bufp == '\n' || *bufp == '\r') {
1919                 patternp++;
1920                 if (*patternp == NULLCHAR)
1921                   continue;
1922                 else
1923                   return FALSE;
1924             } else {
1925                 *matchp++ = *bufp++;
1926                 continue;
1927             }
1928         }
1929         if (*patternp != *bufp) return FALSE;
1930         patternp++;
1931         bufp++;
1932     }
1933 }
1934
1935 void
1936 SendToPlayer(data, length)
1937      char *data;
1938      int length;
1939 {
1940     int error, outCount;
1941     outCount = OutputToProcess(NoProc, data, length, &error);
1942     if (outCount < length) {
1943         DisplayFatalError(_("Error writing to display"), error, 1);
1944     }
1945 }
1946
1947 void
1948 PackHolding(packed, holding)
1949      char packed[];
1950      char *holding;
1951 {
1952     char *p = holding;
1953     char *q = packed;
1954     int runlength = 0;
1955     int curr = 9999;
1956     do {
1957         if (*p == curr) {
1958             runlength++;
1959         } else {
1960             switch (runlength) {
1961               case 0:
1962                 break;
1963               case 1:
1964                 *q++ = curr;
1965                 break;
1966               case 2:
1967                 *q++ = curr;
1968                 *q++ = curr;
1969                 break;
1970               default:
1971                 sprintf(q, "%d", runlength);
1972                 while (*q) q++;
1973                 *q++ = curr;
1974                 break;
1975             }
1976             runlength = 1;
1977             curr = *p;
1978         }
1979     } while (*p++);
1980     *q = NULLCHAR;
1981 }
1982
1983 /* Telnet protocol requests from the front end */
1984 void
1985 TelnetRequest(ddww, option)
1986      unsigned char ddww, option;
1987 {
1988     unsigned char msg[3];
1989     int outCount, outError;
1990
1991     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1992
1993     if (appData.debugMode) {
1994         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1995         switch (ddww) {
1996           case TN_DO:
1997             ddwwStr = "DO";
1998             break;
1999           case TN_DONT:
2000             ddwwStr = "DONT";
2001             break;
2002           case TN_WILL:
2003             ddwwStr = "WILL";
2004             break;
2005           case TN_WONT:
2006             ddwwStr = "WONT";
2007             break;
2008           default:
2009             ddwwStr = buf1;
2010             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2011             break;
2012         }
2013         switch (option) {
2014           case TN_ECHO:
2015             optionStr = "ECHO";
2016             break;
2017           default:
2018             optionStr = buf2;
2019             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2020             break;
2021         }
2022         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2023     }
2024     msg[0] = TN_IAC;
2025     msg[1] = ddww;
2026     msg[2] = option;
2027     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2028     if (outCount < 3) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033 void
2034 DoEcho()
2035 {
2036     if (!appData.icsActive) return;
2037     TelnetRequest(TN_DO, TN_ECHO);
2038 }
2039
2040 void
2041 DontEcho()
2042 {
2043     if (!appData.icsActive) return;
2044     TelnetRequest(TN_DONT, TN_ECHO);
2045 }
2046
2047 void
2048 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2049 {
2050     /* put the holdings sent to us by the server on the board holdings area */
2051     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2052     char p;
2053     ChessSquare piece;
2054
2055     if(gameInfo.holdingsWidth < 2)  return;
2056     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2057         return; // prevent overwriting by pre-board holdings
2058
2059     if( (int)lowestPiece >= BlackPawn ) {
2060         holdingsColumn = 0;
2061         countsColumn = 1;
2062         holdingsStartRow = BOARD_HEIGHT-1;
2063         direction = -1;
2064     } else {
2065         holdingsColumn = BOARD_WIDTH-1;
2066         countsColumn = BOARD_WIDTH-2;
2067         holdingsStartRow = 0;
2068         direction = 1;
2069     }
2070
2071     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2072         board[i][holdingsColumn] = EmptySquare;
2073         board[i][countsColumn]   = (ChessSquare) 0;
2074     }
2075     while( (p=*holdings++) != NULLCHAR ) {
2076         piece = CharToPiece( ToUpper(p) );
2077         if(piece == EmptySquare) continue;
2078         /*j = (int) piece - (int) WhitePawn;*/
2079         j = PieceToNumber(piece);
2080         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2081         if(j < 0) continue;               /* should not happen */
2082         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2083         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2084         board[holdingsStartRow+j*direction][countsColumn]++;
2085     }
2086 }
2087
2088
2089 void
2090 VariantSwitch(Board board, VariantClass newVariant)
2091 {
2092    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2093    static Board oldBoard;
2094
2095    startedFromPositionFile = FALSE;
2096    if(gameInfo.variant == newVariant) return;
2097
2098    /* [HGM] This routine is called each time an assignment is made to
2099     * gameInfo.variant during a game, to make sure the board sizes
2100     * are set to match the new variant. If that means adding or deleting
2101     * holdings, we shift the playing board accordingly
2102     * This kludge is needed because in ICS observe mode, we get boards
2103     * of an ongoing game without knowing the variant, and learn about the
2104     * latter only later. This can be because of the move list we requested,
2105     * in which case the game history is refilled from the beginning anyway,
2106     * but also when receiving holdings of a crazyhouse game. In the latter
2107     * case we want to add those holdings to the already received position.
2108     */
2109
2110
2111    if (appData.debugMode) {
2112      fprintf(debugFP, "Switch board from %s to %s\n",
2113              VariantName(gameInfo.variant), VariantName(newVariant));
2114      setbuf(debugFP, NULL);
2115    }
2116    shuffleOpenings = 0;       /* [HGM] shuffle */
2117    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2118    switch(newVariant)
2119      {
2120      case VariantShogi:
2121        newWidth = 9;  newHeight = 9;
2122        gameInfo.holdingsSize = 7;
2123      case VariantBughouse:
2124      case VariantCrazyhouse:
2125        newHoldingsWidth = 2; break;
2126      case VariantGreat:
2127        newWidth = 10;
2128      case VariantSuper:
2129        newHoldingsWidth = 2;
2130        gameInfo.holdingsSize = 8;
2131        break;
2132      case VariantGothic:
2133      case VariantCapablanca:
2134      case VariantCapaRandom:
2135        newWidth = 10;
2136      default:
2137        newHoldingsWidth = gameInfo.holdingsSize = 0;
2138      };
2139
2140    if(newWidth  != gameInfo.boardWidth  ||
2141       newHeight != gameInfo.boardHeight ||
2142       newHoldingsWidth != gameInfo.holdingsWidth ) {
2143
2144      /* shift position to new playing area, if needed */
2145      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2146        for(i=0; i<BOARD_HEIGHT; i++)
2147          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2148            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2149              board[i][j];
2150        for(i=0; i<newHeight; i++) {
2151          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2152          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2153        }
2154      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2155        for(i=0; i<BOARD_HEIGHT; i++)
2156          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2157            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2158              board[i][j];
2159      }
2160      gameInfo.boardWidth  = newWidth;
2161      gameInfo.boardHeight = newHeight;
2162      gameInfo.holdingsWidth = newHoldingsWidth;
2163      gameInfo.variant = newVariant;
2164      InitDrawingSizes(-2, 0);
2165    } else gameInfo.variant = newVariant;
2166    CopyBoard(oldBoard, board);   // remember correctly formatted board
2167      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2168    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2169 }
2170
2171 static int loggedOn = FALSE;
2172
2173 /*-- Game start info cache: --*/
2174 int gs_gamenum;
2175 char gs_kind[MSG_SIZ];
2176 static char player1Name[128] = "";
2177 static char player2Name[128] = "";
2178 static char cont_seq[] = "\n\\   ";
2179 static int player1Rating = -1;
2180 static int player2Rating = -1;
2181 /*----------------------------*/
2182
2183 ColorClass curColor = ColorNormal;
2184 int suppressKibitz = 0;
2185
2186 // [HGM] seekgraph
2187 Boolean soughtPending = FALSE;
2188 Boolean seekGraphUp;
2189 #define MAX_SEEK_ADS 200
2190 #define SQUARE 0x80
2191 char *seekAdList[MAX_SEEK_ADS];
2192 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2193 float tcList[MAX_SEEK_ADS];
2194 char colorList[MAX_SEEK_ADS];
2195 int nrOfSeekAds = 0;
2196 int minRating = 1010, maxRating = 2800;
2197 int hMargin = 10, vMargin = 20, h, w;
2198 extern int squareSize, lineGap;
2199
2200 void
2201 PlotSeekAd(int i)
2202 {
2203         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2204         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2205         if(r < minRating+100 && r >=0 ) r = minRating+100;
2206         if(r > maxRating) r = maxRating;
2207         if(tc < 1.) tc = 1.;
2208         if(tc > 95.) tc = 95.;
2209         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2210         y = ((double)r - minRating)/(maxRating - minRating)
2211             * (h-vMargin-squareSize/8-1) + vMargin;
2212         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2213         if(strstr(seekAdList[i], " u ")) color = 1;
2214         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2215            !strstr(seekAdList[i], "bullet") &&
2216            !strstr(seekAdList[i], "blitz") &&
2217            !strstr(seekAdList[i], "standard") ) color = 2;
2218         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2219         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2220 }
2221
2222 void
2223 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2224 {
2225         char buf[MSG_SIZ], *ext = "";
2226         VariantClass v = StringToVariant(type);
2227         if(strstr(type, "wild")) {
2228             ext = type + 4; // append wild number
2229             if(v == VariantFischeRandom) type = "chess960"; else
2230             if(v == VariantLoadable) type = "setup"; else
2231             type = VariantName(v);
2232         }
2233         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2234         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2235             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2236             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2237             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2238             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2239             seekNrList[nrOfSeekAds] = nr;
2240             zList[nrOfSeekAds] = 0;
2241             seekAdList[nrOfSeekAds++] = StrSave(buf);
2242             if(plot) PlotSeekAd(nrOfSeekAds-1);
2243         }
2244 }
2245
2246 void
2247 EraseSeekDot(int i)
2248 {
2249     int x = xList[i], y = yList[i], d=squareSize/4, k;
2250     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2251     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2252     // now replot every dot that overlapped
2253     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2254         int xx = xList[k], yy = yList[k];
2255         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2256             DrawSeekDot(xx, yy, colorList[k]);
2257     }
2258 }
2259
2260 void
2261 RemoveSeekAd(int nr)
2262 {
2263         int i;
2264         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2265             EraseSeekDot(i);
2266             if(seekAdList[i]) free(seekAdList[i]);
2267             seekAdList[i] = seekAdList[--nrOfSeekAds];
2268             seekNrList[i] = seekNrList[nrOfSeekAds];
2269             ratingList[i] = ratingList[nrOfSeekAds];
2270             colorList[i]  = colorList[nrOfSeekAds];
2271             tcList[i] = tcList[nrOfSeekAds];
2272             xList[i]  = xList[nrOfSeekAds];
2273             yList[i]  = yList[nrOfSeekAds];
2274             zList[i]  = zList[nrOfSeekAds];
2275             seekAdList[nrOfSeekAds] = NULL;
2276             break;
2277         }
2278 }
2279
2280 Boolean
2281 MatchSoughtLine(char *line)
2282 {
2283     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2284     int nr, base, inc, u=0; char dummy;
2285
2286     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2287        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2288        (u=1) &&
2289        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2290         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2291         // match: compact and save the line
2292         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2293         return TRUE;
2294     }
2295     return FALSE;
2296 }
2297
2298 int
2299 DrawSeekGraph()
2300 {
2301     int i;
2302     if(!seekGraphUp) return FALSE;
2303     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2304     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2305
2306     DrawSeekBackground(0, 0, w, h);
2307     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2308     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2309     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2310         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2311         yy = h-1-yy;
2312         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2313         if(i%500 == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2317         }
2318     }
2319     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2320     for(i=1; i<100; i+=(i<10?1:5)) {
2321         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2322         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2323         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2324             char buf[MSG_SIZ];
2325             snprintf(buf, MSG_SIZ, "%d", i);
2326             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2327         }
2328     }
2329     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2330     return TRUE;
2331 }
2332
2333 int SeekGraphClick(ClickType click, int x, int y, int moving)
2334 {
2335     static int lastDown = 0, displayed = 0, lastSecond;
2336     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2337         if(click == Release || moving) return FALSE;
2338         nrOfSeekAds = 0;
2339         soughtPending = TRUE;
2340         SendToICS(ics_prefix);
2341         SendToICS("sought\n"); // should this be "sought all"?
2342     } else { // issue challenge based on clicked ad
2343         int dist = 10000; int i, closest = 0, second = 0;
2344         for(i=0; i<nrOfSeekAds; i++) {
2345             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2346             if(d < dist) { dist = d; closest = i; }
2347             second += (d - zList[i] < 120); // count in-range ads
2348             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2349         }
2350         if(dist < 120) {
2351             char buf[MSG_SIZ];
2352             second = (second > 1);
2353             if(displayed != closest || second != lastSecond) {
2354                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2355                 lastSecond = second; displayed = closest;
2356             }
2357             if(click == Press) {
2358                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2359                 lastDown = closest;
2360                 return TRUE;
2361             } // on press 'hit', only show info
2362             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2363             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2364             SendToICS(ics_prefix);
2365             SendToICS(buf);
2366             return TRUE; // let incoming board of started game pop down the graph
2367         } else if(click == Release) { // release 'miss' is ignored
2368             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2369             if(moving == 2) { // right up-click
2370                 nrOfSeekAds = 0; // refresh graph
2371                 soughtPending = TRUE;
2372                 SendToICS(ics_prefix);
2373                 SendToICS("sought\n"); // should this be "sought all"?
2374             }
2375             return TRUE;
2376         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2377         // press miss or release hit 'pop down' seek graph
2378         seekGraphUp = FALSE;
2379         DrawPosition(TRUE, NULL);
2380     }
2381     return TRUE;
2382 }
2383
2384 void
2385 read_from_ics(isr, closure, data, count, error)
2386      InputSourceRef isr;
2387      VOIDSTAR closure;
2388      char *data;
2389      int count;
2390      int error;
2391 {
2392 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2393 #define STARTED_NONE 0
2394 #define STARTED_MOVES 1
2395 #define STARTED_BOARD 2
2396 #define STARTED_OBSERVE 3
2397 #define STARTED_HOLDINGS 4
2398 #define STARTED_CHATTER 5
2399 #define STARTED_COMMENT 6
2400 #define STARTED_MOVES_NOHIDE 7
2401
2402     static int started = STARTED_NONE;
2403     static char parse[20000];
2404     static int parse_pos = 0;
2405     static char buf[BUF_SIZE + 1];
2406     static int firstTime = TRUE, intfSet = FALSE;
2407     static ColorClass prevColor = ColorNormal;
2408     static int savingComment = FALSE;
2409     static int cmatch = 0; // continuation sequence match
2410     char *bp;
2411     char str[MSG_SIZ];
2412     int i, oldi;
2413     int buf_len;
2414     int next_out;
2415     int tkind;
2416     int backup;    /* [DM] For zippy color lines */
2417     char *p;
2418     char talker[MSG_SIZ]; // [HGM] chat
2419     int channel;
2420
2421     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2422
2423     if (appData.debugMode) {
2424       if (!error) {
2425         fprintf(debugFP, "<ICS: ");
2426         show_bytes(debugFP, data, count);
2427         fprintf(debugFP, "\n");
2428       }
2429     }
2430
2431     if (appData.debugMode) { int f = forwardMostMove;
2432         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2433                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2434                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2435     }
2436     if (count > 0) {
2437         /* If last read ended with a partial line that we couldn't parse,
2438            prepend it to the new read and try again. */
2439         if (leftover_len > 0) {
2440             for (i=0; i<leftover_len; i++)
2441               buf[i] = buf[leftover_start + i];
2442         }
2443
2444     /* copy new characters into the buffer */
2445     bp = buf + leftover_len;
2446     buf_len=leftover_len;
2447     for (i=0; i<count; i++)
2448     {
2449         // ignore these
2450         if (data[i] == '\r')
2451             continue;
2452
2453         // join lines split by ICS?
2454         if (!appData.noJoin)
2455         {
2456             /*
2457                 Joining just consists of finding matches against the
2458                 continuation sequence, and discarding that sequence
2459                 if found instead of copying it.  So, until a match
2460                 fails, there's nothing to do since it might be the
2461                 complete sequence, and thus, something we don't want
2462                 copied.
2463             */
2464             if (data[i] == cont_seq[cmatch])
2465             {
2466                 cmatch++;
2467                 if (cmatch == strlen(cont_seq))
2468                 {
2469                     cmatch = 0; // complete match.  just reset the counter
2470
2471                     /*
2472                         it's possible for the ICS to not include the space
2473                         at the end of the last word, making our [correct]
2474                         join operation fuse two separate words.  the server
2475                         does this when the space occurs at the width setting.
2476                     */
2477                     if (!buf_len || buf[buf_len-1] != ' ')
2478                     {
2479                         *bp++ = ' ';
2480                         buf_len++;
2481                     }
2482                 }
2483                 continue;
2484             }
2485             else if (cmatch)
2486             {
2487                 /*
2488                     match failed, so we have to copy what matched before
2489                     falling through and copying this character.  In reality,
2490                     this will only ever be just the newline character, but
2491                     it doesn't hurt to be precise.
2492                 */
2493                 strncpy(bp, cont_seq, cmatch);
2494                 bp += cmatch;
2495                 buf_len += cmatch;
2496                 cmatch = 0;
2497             }
2498         }
2499
2500         // copy this char
2501         *bp++ = data[i];
2502         buf_len++;
2503     }
2504
2505         buf[buf_len] = NULLCHAR;
2506 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2507         next_out = 0;
2508         leftover_start = 0;
2509
2510         i = 0;
2511         while (i < buf_len) {
2512             /* Deal with part of the TELNET option negotiation
2513                protocol.  We refuse to do anything beyond the
2514                defaults, except that we allow the WILL ECHO option,
2515                which ICS uses to turn off password echoing when we are
2516                directly connected to it.  We reject this option
2517                if localLineEditing mode is on (always on in xboard)
2518                and we are talking to port 23, which might be a real
2519                telnet server that will try to keep WILL ECHO on permanently.
2520              */
2521             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2522                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2523                 unsigned char option;
2524                 oldi = i;
2525                 switch ((unsigned char) buf[++i]) {
2526                   case TN_WILL:
2527                     if (appData.debugMode)
2528                       fprintf(debugFP, "\n<WILL ");
2529                     switch (option = (unsigned char) buf[++i]) {
2530                       case TN_ECHO:
2531                         if (appData.debugMode)
2532                           fprintf(debugFP, "ECHO ");
2533                         /* Reply only if this is a change, according
2534                            to the protocol rules. */
2535                         if (remoteEchoOption) break;
2536                         if (appData.localLineEditing &&
2537                             atoi(appData.icsPort) == TN_PORT) {
2538                             TelnetRequest(TN_DONT, TN_ECHO);
2539                         } else {
2540                             EchoOff();
2541                             TelnetRequest(TN_DO, TN_ECHO);
2542                             remoteEchoOption = TRUE;
2543                         }
2544                         break;
2545                       default:
2546                         if (appData.debugMode)
2547                           fprintf(debugFP, "%d ", option);
2548                         /* Whatever this is, we don't want it. */
2549                         TelnetRequest(TN_DONT, option);
2550                         break;
2551                     }
2552                     break;
2553                   case TN_WONT:
2554                     if (appData.debugMode)
2555                       fprintf(debugFP, "\n<WONT ");
2556                     switch (option = (unsigned char) buf[++i]) {
2557                       case TN_ECHO:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "ECHO ");
2560                         /* Reply only if this is a change, according
2561                            to the protocol rules. */
2562                         if (!remoteEchoOption) break;
2563                         EchoOn();
2564                         TelnetRequest(TN_DONT, TN_ECHO);
2565                         remoteEchoOption = FALSE;
2566                         break;
2567                       default:
2568                         if (appData.debugMode)
2569                           fprintf(debugFP, "%d ", (unsigned char) option);
2570                         /* Whatever this is, it must already be turned
2571                            off, because we never agree to turn on
2572                            anything non-default, so according to the
2573                            protocol rules, we don't reply. */
2574                         break;
2575                     }
2576                     break;
2577                   case TN_DO:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<DO ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       default:
2582                         /* Whatever this is, we refuse to do it. */
2583                         if (appData.debugMode)
2584                           fprintf(debugFP, "%d ", option);
2585                         TelnetRequest(TN_WONT, option);
2586                         break;
2587                     }
2588                     break;
2589                   case TN_DONT:
2590                     if (appData.debugMode)
2591                       fprintf(debugFP, "\n<DONT ");
2592                     switch (option = (unsigned char) buf[++i]) {
2593                       default:
2594                         if (appData.debugMode)
2595                           fprintf(debugFP, "%d ", option);
2596                         /* Whatever this is, we are already not doing
2597                            it, because we never agree to do anything
2598                            non-default, so according to the protocol
2599                            rules, we don't reply. */
2600                         break;
2601                     }
2602                     break;
2603                   case TN_IAC:
2604                     if (appData.debugMode)
2605                       fprintf(debugFP, "\n<IAC ");
2606                     /* Doubled IAC; pass it through */
2607                     i--;
2608                     break;
2609                   default:
2610                     if (appData.debugMode)
2611                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2612                     /* Drop all other telnet commands on the floor */
2613                     break;
2614                 }
2615                 if (oldi > next_out)
2616                   SendToPlayer(&buf[next_out], oldi - next_out);
2617                 if (++i > next_out)
2618                   next_out = i;
2619                 continue;
2620             }
2621
2622             /* OK, this at least will *usually* work */
2623             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2624                 loggedOn = TRUE;
2625             }
2626
2627             if (loggedOn && !intfSet) {
2628                 if (ics_type == ICS_ICC) {
2629                   snprintf(str, MSG_SIZ,
2630                           "/set-quietly interface %s\n/set-quietly style 12\n",
2631                           programVersion);
2632                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2634                 } else if (ics_type == ICS_CHESSNET) {
2635                   snprintf(str, MSG_SIZ, "/style 12\n");
2636                 } else {
2637                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2638                   strcat(str, programVersion);
2639                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2640                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2641                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2642 #ifdef WIN32
2643                   strcat(str, "$iset nohighlight 1\n");
2644 #endif
2645                   strcat(str, "$iset lock 1\n$style 12\n");
2646                 }
2647                 SendToICS(str);
2648                 NotifyFrontendLogin();
2649                 intfSet = TRUE;
2650             }
2651
2652             if (started == STARTED_COMMENT) {
2653                 /* Accumulate characters in comment */
2654                 parse[parse_pos++] = buf[i];
2655                 if (buf[i] == '\n') {
2656                     parse[parse_pos] = NULLCHAR;
2657                     if(chattingPartner>=0) {
2658                         char mess[MSG_SIZ];
2659                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2660                         OutputChatMessage(chattingPartner, mess);
2661                         chattingPartner = -1;
2662                         next_out = i+1; // [HGM] suppress printing in ICS window
2663                     } else
2664                     if(!suppressKibitz) // [HGM] kibitz
2665                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2666                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2667                         int nrDigit = 0, nrAlph = 0, j;
2668                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2669                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2670                         parse[parse_pos] = NULLCHAR;
2671                         // try to be smart: if it does not look like search info, it should go to
2672                         // ICS interaction window after all, not to engine-output window.
2673                         for(j=0; j<parse_pos; j++) { // count letters and digits
2674                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2675                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2676                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2677                         }
2678                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2679                             int depth=0; float score;
2680                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2681                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2682                                 pvInfoList[forwardMostMove-1].depth = depth;
2683                                 pvInfoList[forwardMostMove-1].score = 100*score;
2684                             }
2685                             OutputKibitz(suppressKibitz, parse);
2686                         } else {
2687                             char tmp[MSG_SIZ];
2688                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2689                             SendToPlayer(tmp, strlen(tmp));
2690                         }
2691                         next_out = i+1; // [HGM] suppress printing in ICS window
2692                     }
2693                     started = STARTED_NONE;
2694                 } else {
2695                     /* Don't match patterns against characters in comment */
2696                     i++;
2697                     continue;
2698                 }
2699             }
2700             if (started == STARTED_CHATTER) {
2701                 if (buf[i] != '\n') {
2702                     /* Don't match patterns against characters in chatter */
2703                     i++;
2704                     continue;
2705                 }
2706                 started = STARTED_NONE;
2707                 if(suppressKibitz) next_out = i+1;
2708             }
2709
2710             /* Kludge to deal with rcmd protocol */
2711             if (firstTime && looking_at(buf, &i, "\001*")) {
2712                 DisplayFatalError(&buf[1], 0, 1);
2713                 continue;
2714             } else {
2715                 firstTime = FALSE;
2716             }
2717
2718             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2719                 ics_type = ICS_ICC;
2720                 ics_prefix = "/";
2721                 if (appData.debugMode)
2722                   fprintf(debugFP, "ics_type %d\n", ics_type);
2723                 continue;
2724             }
2725             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2726                 ics_type = ICS_FICS;
2727                 ics_prefix = "$";
2728                 if (appData.debugMode)
2729                   fprintf(debugFP, "ics_type %d\n", ics_type);
2730                 continue;
2731             }
2732             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2733                 ics_type = ICS_CHESSNET;
2734                 ics_prefix = "/";
2735                 if (appData.debugMode)
2736                   fprintf(debugFP, "ics_type %d\n", ics_type);
2737                 continue;
2738             }
2739
2740             if (!loggedOn &&
2741                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2742                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2743                  looking_at(buf, &i, "will be \"*\""))) {
2744               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2745               continue;
2746             }
2747
2748             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2749               char buf[MSG_SIZ];
2750               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2751               DisplayIcsInteractionTitle(buf);
2752               have_set_title = TRUE;
2753             }
2754
2755             /* skip finger notes */
2756             if (started == STARTED_NONE &&
2757                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2758                  (buf[i] == '1' && buf[i+1] == '0')) &&
2759                 buf[i+2] == ':' && buf[i+3] == ' ') {
2760               started = STARTED_CHATTER;
2761               i += 3;
2762               continue;
2763             }
2764
2765             oldi = i;
2766             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2767             if(appData.seekGraph) {
2768                 if(soughtPending && MatchSoughtLine(buf+i)) {
2769                     i = strstr(buf+i, "rated") - buf;
2770                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2771                     next_out = leftover_start = i;
2772                     started = STARTED_CHATTER;
2773                     suppressKibitz = TRUE;
2774                     continue;
2775                 }
2776                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2777                         && looking_at(buf, &i, "* ads displayed")) {
2778                     soughtPending = FALSE;
2779                     seekGraphUp = TRUE;
2780                     DrawSeekGraph();
2781                     continue;
2782                 }
2783                 if(appData.autoRefresh) {
2784                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2785                         int s = (ics_type == ICS_ICC); // ICC format differs
2786                         if(seekGraphUp)
2787                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2788                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2789                         looking_at(buf, &i, "*% "); // eat prompt
2790                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2791                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2792                         next_out = i; // suppress
2793                         continue;
2794                     }
2795                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2796                         char *p = star_match[0];
2797                         while(*p) {
2798                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2799                             while(*p && *p++ != ' '); // next
2800                         }
2801                         looking_at(buf, &i, "*% "); // eat prompt
2802                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2803                         next_out = i;
2804                         continue;
2805                     }
2806                 }
2807             }
2808
2809             /* skip formula vars */
2810             if (started == STARTED_NONE &&
2811                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2812               started = STARTED_CHATTER;
2813               i += 3;
2814               continue;
2815             }
2816
2817             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2818             if (appData.autoKibitz && started == STARTED_NONE &&
2819                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2820                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2821                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2822                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2823                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2824                         suppressKibitz = TRUE;
2825                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2826                         next_out = i;
2827                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2828                                 && (gameMode == IcsPlayingWhite)) ||
2829                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2830                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2831                             started = STARTED_CHATTER; // own kibitz we simply discard
2832                         else {
2833                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2834                             parse_pos = 0; parse[0] = NULLCHAR;
2835                             savingComment = TRUE;
2836                             suppressKibitz = gameMode != IcsObserving ? 2 :
2837                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2838                         }
2839                         continue;
2840                 } else
2841                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2842                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2843                          && atoi(star_match[0])) {
2844                     // suppress the acknowledgements of our own autoKibitz
2845                     char *p;
2846                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2847                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2848                     SendToPlayer(star_match[0], strlen(star_match[0]));
2849                     if(looking_at(buf, &i, "*% ")) // eat prompt
2850                         suppressKibitz = FALSE;
2851                     next_out = i;
2852                     continue;
2853                 }
2854             } // [HGM] kibitz: end of patch
2855
2856             // [HGM] chat: intercept tells by users for which we have an open chat window
2857             channel = -1;
2858             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2859                                            looking_at(buf, &i, "* whispers:") ||
2860                                            looking_at(buf, &i, "* kibitzes:") ||
2861                                            looking_at(buf, &i, "* shouts:") ||
2862                                            looking_at(buf, &i, "* c-shouts:") ||
2863                                            looking_at(buf, &i, "--> * ") ||
2864                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2865                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2866                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2867                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2868                 int p;
2869                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2870                 chattingPartner = -1;
2871
2872                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2873                 for(p=0; p<MAX_CHAT; p++) {
2874                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2875                     talker[0] = '['; strcat(talker, "] ");
2876                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2877                     chattingPartner = p; break;
2878                     }
2879                 } else
2880                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2881                 for(p=0; p<MAX_CHAT; p++) {
2882                     if(!strcmp("kibitzes", chatPartner[p])) {
2883                         talker[0] = '['; strcat(talker, "] ");
2884                         chattingPartner = p; break;
2885                     }
2886                 } else
2887                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2888                 for(p=0; p<MAX_CHAT; p++) {
2889                     if(!strcmp("whispers", chatPartner[p])) {
2890                         talker[0] = '['; strcat(talker, "] ");
2891                         chattingPartner = p; break;
2892                     }
2893                 } else
2894                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2895                   if(buf[i-8] == '-' && buf[i-3] == 't')
2896                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2897                     if(!strcmp("c-shouts", chatPartner[p])) {
2898                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2899                         chattingPartner = p; break;
2900                     }
2901                   }
2902                   if(chattingPartner < 0)
2903                   for(p=0; p<MAX_CHAT; p++) {
2904                     if(!strcmp("shouts", chatPartner[p])) {
2905                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2906                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2907                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2908                         chattingPartner = p; break;
2909                     }
2910                   }
2911                 }
2912                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2913                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2914                     talker[0] = 0; Colorize(ColorTell, FALSE);
2915                     chattingPartner = p; break;
2916                 }
2917                 if(chattingPartner<0) i = oldi; else {
2918                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2919                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2920                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2921                     started = STARTED_COMMENT;
2922                     parse_pos = 0; parse[0] = NULLCHAR;
2923                     savingComment = 3 + chattingPartner; // counts as TRUE
2924                     suppressKibitz = TRUE;
2925                     continue;
2926                 }
2927             } // [HGM] chat: end of patch
2928
2929             if (appData.zippyTalk || appData.zippyPlay) {
2930                 /* [DM] Backup address for color zippy lines */
2931                 backup = i;
2932 #if ZIPPY
2933                if (loggedOn == TRUE)
2934                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2935                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2936 #endif
2937             } // [DM] 'else { ' deleted
2938                 if (
2939                     /* Regular tells and says */
2940                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2941                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2942                     looking_at(buf, &i, "* says: ") ||
2943                     /* Don't color "message" or "messages" output */
2944                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2945                     looking_at(buf, &i, "*. * at *:*: ") ||
2946                     looking_at(buf, &i, "--* (*:*): ") ||
2947                     /* Message notifications (same color as tells) */
2948                     looking_at(buf, &i, "* has left a message ") ||
2949                     looking_at(buf, &i, "* just sent you a message:\n") ||
2950                     /* Whispers and kibitzes */
2951                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2952                     looking_at(buf, &i, "* kibitzes: ") ||
2953                     /* Channel tells */
2954                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2955
2956                   if (tkind == 1 && strchr(star_match[0], ':')) {
2957                       /* Avoid "tells you:" spoofs in channels */
2958                      tkind = 3;
2959                   }
2960                   if (star_match[0][0] == NULLCHAR ||
2961                       strchr(star_match[0], ' ') ||
2962                       (tkind == 3 && strchr(star_match[1], ' '))) {
2963                     /* Reject bogus matches */
2964                     i = oldi;
2965                   } else {
2966                     if (appData.colorize) {
2967                       if (oldi > next_out) {
2968                         SendToPlayer(&buf[next_out], oldi - next_out);
2969                         next_out = oldi;
2970                       }
2971                       switch (tkind) {
2972                       case 1:
2973                         Colorize(ColorTell, FALSE);
2974                         curColor = ColorTell;
2975                         break;
2976                       case 2:
2977                         Colorize(ColorKibitz, FALSE);
2978                         curColor = ColorKibitz;
2979                         break;
2980                       case 3:
2981                         p = strrchr(star_match[1], '(');
2982                         if (p == NULL) {
2983                           p = star_match[1];
2984                         } else {
2985                           p++;
2986                         }
2987                         if (atoi(p) == 1) {
2988                           Colorize(ColorChannel1, FALSE);
2989                           curColor = ColorChannel1;
2990                         } else {
2991                           Colorize(ColorChannel, FALSE);
2992                           curColor = ColorChannel;
2993                         }
2994                         break;
2995                       case 5:
2996                         curColor = ColorNormal;
2997                         break;
2998                       }
2999                     }
3000                     if (started == STARTED_NONE && appData.autoComment &&
3001                         (gameMode == IcsObserving ||
3002                          gameMode == IcsPlayingWhite ||
3003                          gameMode == IcsPlayingBlack)) {
3004                       parse_pos = i - oldi;
3005                       memcpy(parse, &buf[oldi], parse_pos);
3006                       parse[parse_pos] = NULLCHAR;
3007                       started = STARTED_COMMENT;
3008                       savingComment = TRUE;
3009                     } else {
3010                       started = STARTED_CHATTER;
3011                       savingComment = FALSE;
3012                     }
3013                     loggedOn = TRUE;
3014                     continue;
3015                   }
3016                 }
3017
3018                 if (looking_at(buf, &i, "* s-shouts: ") ||
3019                     looking_at(buf, &i, "* c-shouts: ")) {
3020                     if (appData.colorize) {
3021                         if (oldi > next_out) {
3022                             SendToPlayer(&buf[next_out], oldi - next_out);
3023                             next_out = oldi;
3024                         }
3025                         Colorize(ColorSShout, FALSE);
3026                         curColor = ColorSShout;
3027                     }
3028                     loggedOn = TRUE;
3029                     started = STARTED_CHATTER;
3030                     continue;
3031                 }
3032
3033                 if (looking_at(buf, &i, "--->")) {
3034                     loggedOn = TRUE;
3035                     continue;
3036                 }
3037
3038                 if (looking_at(buf, &i, "* shouts: ") ||
3039                     looking_at(buf, &i, "--> ")) {
3040                     if (appData.colorize) {
3041                         if (oldi > next_out) {
3042                             SendToPlayer(&buf[next_out], oldi - next_out);
3043                             next_out = oldi;
3044                         }
3045                         Colorize(ColorShout, FALSE);
3046                         curColor = ColorShout;
3047                     }
3048                     loggedOn = TRUE;
3049                     started = STARTED_CHATTER;
3050                     continue;
3051                 }
3052
3053                 if (looking_at( buf, &i, "Challenge:")) {
3054                     if (appData.colorize) {
3055                         if (oldi > next_out) {
3056                             SendToPlayer(&buf[next_out], oldi - next_out);
3057                             next_out = oldi;
3058                         }
3059                         Colorize(ColorChallenge, FALSE);
3060                         curColor = ColorChallenge;
3061                     }
3062                     loggedOn = TRUE;
3063                     continue;
3064                 }
3065
3066                 if (looking_at(buf, &i, "* offers you") ||
3067                     looking_at(buf, &i, "* offers to be") ||
3068                     looking_at(buf, &i, "* would like to") ||
3069                     looking_at(buf, &i, "* requests to") ||
3070                     looking_at(buf, &i, "Your opponent offers") ||
3071                     looking_at(buf, &i, "Your opponent requests")) {
3072
3073                     if (appData.colorize) {
3074                         if (oldi > next_out) {
3075                             SendToPlayer(&buf[next_out], oldi - next_out);
3076                             next_out = oldi;
3077                         }
3078                         Colorize(ColorRequest, FALSE);
3079                         curColor = ColorRequest;
3080                     }
3081                     continue;
3082                 }
3083
3084                 if (looking_at(buf, &i, "* (*) seeking")) {
3085                     if (appData.colorize) {
3086                         if (oldi > next_out) {
3087                             SendToPlayer(&buf[next_out], oldi - next_out);
3088                             next_out = oldi;
3089                         }
3090                         Colorize(ColorSeek, FALSE);
3091                         curColor = ColorSeek;
3092                     }
3093                     continue;
3094             }
3095
3096             if (looking_at(buf, &i, "\\   ")) {
3097                 if (prevColor != ColorNormal) {
3098                     if (oldi > next_out) {
3099                         SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = oldi;
3101                     }
3102                     Colorize(prevColor, TRUE);
3103                     curColor = prevColor;
3104                 }
3105                 if (savingComment) {
3106                     parse_pos = i - oldi;
3107                     memcpy(parse, &buf[oldi], parse_pos);
3108                     parse[parse_pos] = NULLCHAR;
3109                     started = STARTED_COMMENT;
3110                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3111                         chattingPartner = savingComment - 3; // kludge to remember the box
3112                 } else {
3113                     started = STARTED_CHATTER;
3114                 }
3115                 continue;
3116             }
3117
3118             if (looking_at(buf, &i, "Black Strength :") ||
3119                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3120                 looking_at(buf, &i, "<10>") ||
3121                 looking_at(buf, &i, "#@#")) {
3122                 /* Wrong board style */
3123                 loggedOn = TRUE;
3124                 SendToICS(ics_prefix);
3125                 SendToICS("set style 12\n");
3126                 SendToICS(ics_prefix);
3127                 SendToICS("refresh\n");
3128                 continue;
3129             }
3130
3131             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3132                 ICSInitScript();
3133                 have_sent_ICS_logon = 1;
3134                 continue;
3135             }
3136
3137             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3138                 (looking_at(buf, &i, "\n<12> ") ||
3139                  looking_at(buf, &i, "<12> "))) {
3140                 loggedOn = TRUE;
3141                 if (oldi > next_out) {
3142                     SendToPlayer(&buf[next_out], oldi - next_out);
3143                 }
3144                 next_out = i;
3145                 started = STARTED_BOARD;
3146                 parse_pos = 0;
3147                 continue;
3148             }
3149
3150             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3151                 looking_at(buf, &i, "<b1> ")) {
3152                 if (oldi > next_out) {
3153                     SendToPlayer(&buf[next_out], oldi - next_out);
3154                 }
3155                 next_out = i;
3156                 started = STARTED_HOLDINGS;
3157                 parse_pos = 0;
3158                 continue;
3159             }
3160
3161             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3162                 loggedOn = TRUE;
3163                 /* Header for a move list -- first line */
3164
3165                 switch (ics_getting_history) {
3166                   case H_FALSE:
3167                     switch (gameMode) {
3168                       case IcsIdle:
3169                       case BeginningOfGame:
3170                         /* User typed "moves" or "oldmoves" while we
3171                            were idle.  Pretend we asked for these
3172                            moves and soak them up so user can step
3173                            through them and/or save them.
3174                            */
3175                         Reset(FALSE, TRUE);
3176                         gameMode = IcsObserving;
3177                         ModeHighlight();
3178                         ics_gamenum = -1;
3179                         ics_getting_history = H_GOT_UNREQ_HEADER;
3180                         break;
3181                       case EditGame: /*?*/
3182                       case EditPosition: /*?*/
3183                         /* Should above feature work in these modes too? */
3184                         /* For now it doesn't */
3185                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3186                         break;
3187                       default:
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                     }
3191                     break;
3192                   case H_REQUESTED:
3193                     /* Is this the right one? */
3194                     if (gameInfo.white && gameInfo.black &&
3195                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3196                         strcmp(gameInfo.black, star_match[2]) == 0) {
3197                         /* All is well */
3198                         ics_getting_history = H_GOT_REQ_HEADER;
3199                     }
3200                     break;
3201                   case H_GOT_REQ_HEADER:
3202                   case H_GOT_UNREQ_HEADER:
3203                   case H_GOT_UNWANTED_HEADER:
3204                   case H_GETTING_MOVES:
3205                     /* Should not happen */
3206                     DisplayError(_("Error gathering move list: two headers"), 0);
3207                     ics_getting_history = H_FALSE;
3208                     break;
3209                 }
3210
3211                 /* Save player ratings into gameInfo if needed */
3212                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3213                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3214                     (gameInfo.whiteRating == -1 ||
3215                      gameInfo.blackRating == -1)) {
3216
3217                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3218                     gameInfo.blackRating = string_to_rating(star_match[3]);
3219                     if (appData.debugMode)
3220                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3221                               gameInfo.whiteRating, gameInfo.blackRating);
3222                 }
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i,
3227               "* * match, initial time: * minute*, increment: * second")) {
3228                 /* Header for a move list -- second line */
3229                 /* Initial board will follow if this is a wild game */
3230                 if (gameInfo.event != NULL) free(gameInfo.event);
3231                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3232                 gameInfo.event = StrSave(str);
3233                 /* [HGM] we switched variant. Translate boards if needed. */
3234                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3235                 continue;
3236             }
3237
3238             if (looking_at(buf, &i, "Move  ")) {
3239                 /* Beginning of a move list */
3240                 switch (ics_getting_history) {
3241                   case H_FALSE:
3242                     /* Normally should not happen */
3243                     /* Maybe user hit reset while we were parsing */
3244                     break;
3245                   case H_REQUESTED:
3246                     /* Happens if we are ignoring a move list that is not
3247                      * the one we just requested.  Common if the user
3248                      * tries to observe two games without turning off
3249                      * getMoveList */
3250                     break;
3251                   case H_GETTING_MOVES:
3252                     /* Should not happen */
3253                     DisplayError(_("Error gathering move list: nested"), 0);
3254                     ics_getting_history = H_FALSE;
3255                     break;
3256                   case H_GOT_REQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES;
3259                     parse_pos = 0;
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                     }
3263                     break;
3264                   case H_GOT_UNREQ_HEADER:
3265                     ics_getting_history = H_GETTING_MOVES;
3266                     started = STARTED_MOVES_NOHIDE;
3267                     parse_pos = 0;
3268                     break;
3269                   case H_GOT_UNWANTED_HEADER:
3270                     ics_getting_history = H_FALSE;
3271                     break;
3272                 }
3273                 continue;
3274             }
3275
3276             if (looking_at(buf, &i, "% ") ||
3277                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3278                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3279                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3280                     soughtPending = FALSE;
3281                     seekGraphUp = TRUE;
3282                     DrawSeekGraph();
3283                 }
3284                 if(suppressKibitz) next_out = i;
3285                 savingComment = FALSE;
3286                 suppressKibitz = 0;
3287                 switch (started) {
3288                   case STARTED_MOVES:
3289                   case STARTED_MOVES_NOHIDE:
3290                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3291                     parse[parse_pos + i - oldi] = NULLCHAR;
3292                     ParseGameHistory(parse);
3293 #if ZIPPY
3294                     if (appData.zippyPlay && first.initDone) {
3295                         FeedMovesToProgram(&first, forwardMostMove);
3296                         if (gameMode == IcsPlayingWhite) {
3297                             if (WhiteOnMove(forwardMostMove)) {
3298                                 if (first.sendTime) {
3299                                   if (first.useColors) {
3300                                     SendToProgram("black\n", &first);
3301                                   }
3302                                   SendTimeRemaining(&first, TRUE);
3303                                 }
3304                                 if (first.useColors) {
3305                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3306                                 }
3307                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3308                                 first.maybeThinking = TRUE;
3309                             } else {
3310                                 if (first.usePlayother) {
3311                                   if (first.sendTime) {
3312                                     SendTimeRemaining(&first, TRUE);
3313                                   }
3314                                   SendToProgram("playother\n", &first);
3315                                   firstMove = FALSE;
3316                                 } else {
3317                                   firstMove = TRUE;
3318                                 }
3319                             }
3320                         } else if (gameMode == IcsPlayingBlack) {
3321                             if (!WhiteOnMove(forwardMostMove)) {
3322                                 if (first.sendTime) {
3323                                   if (first.useColors) {
3324                                     SendToProgram("white\n", &first);
3325                                   }
3326                                   SendTimeRemaining(&first, FALSE);
3327                                 }
3328                                 if (first.useColors) {
3329                                   SendToProgram("black\n", &first);
3330                                 }
3331                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3332                                 first.maybeThinking = TRUE;
3333                             } else {
3334                                 if (first.usePlayother) {
3335                                   if (first.sendTime) {
3336                                     SendTimeRemaining(&first, FALSE);
3337                                   }
3338                                   SendToProgram("playother\n", &first);
3339                                   firstMove = FALSE;
3340                                 } else {
3341                                   firstMove = TRUE;
3342                                 }
3343                             }
3344                         }
3345                     }
3346 #endif
3347                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3348                         /* Moves came from oldmoves or moves command
3349                            while we weren't doing anything else.
3350                            */
3351                         currentMove = forwardMostMove;
3352                         ClearHighlights();/*!!could figure this out*/
3353                         flipView = appData.flipView;
3354                         DrawPosition(TRUE, boards[currentMove]);
3355                         DisplayBothClocks();
3356                         snprintf(str, MSG_SIZ, "%s vs. %s",
3357                                 gameInfo.white, gameInfo.black);
3358                         DisplayTitle(str);
3359                         gameMode = IcsIdle;
3360                     } else {
3361                         /* Moves were history of an active game */
3362                         if (gameInfo.resultDetails != NULL) {
3363                             free(gameInfo.resultDetails);
3364                             gameInfo.resultDetails = NULL;
3365                         }
3366                     }
3367                     HistorySet(parseList, backwardMostMove,
3368                                forwardMostMove, currentMove-1);
3369                     DisplayMove(currentMove - 1);
3370                     if (started == STARTED_MOVES) next_out = i;
3371                     started = STARTED_NONE;
3372                     ics_getting_history = H_FALSE;
3373                     break;
3374
3375                   case STARTED_OBSERVE:
3376                     started = STARTED_NONE;
3377                     SendToICS(ics_prefix);
3378                     SendToICS("refresh\n");
3379                     break;
3380
3381                   default:
3382                     break;
3383                 }
3384                 if(bookHit) { // [HGM] book: simulate book reply
3385                     static char bookMove[MSG_SIZ]; // a bit generous?
3386
3387                     programStats.nodes = programStats.depth = programStats.time =
3388                     programStats.score = programStats.got_only_move = 0;
3389                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3390
3391                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3392                     strcat(bookMove, bookHit);
3393                     HandleMachineMove(bookMove, &first);
3394                 }
3395                 continue;
3396             }
3397
3398             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3399                  started == STARTED_HOLDINGS ||
3400                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3401                 /* Accumulate characters in move list or board */
3402                 parse[parse_pos++] = buf[i];
3403             }
3404
3405             /* Start of game messages.  Mostly we detect start of game
3406                when the first board image arrives.  On some versions
3407                of the ICS, though, we need to do a "refresh" after starting
3408                to observe in order to get the current board right away. */
3409             if (looking_at(buf, &i, "Adding game * to observation list")) {
3410                 started = STARTED_OBSERVE;
3411                 continue;
3412             }
3413
3414             /* Handle auto-observe */
3415             if (appData.autoObserve &&
3416                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3417                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3418                 char *player;
3419                 /* Choose the player that was highlighted, if any. */
3420                 if (star_match[0][0] == '\033' ||
3421                     star_match[1][0] != '\033') {
3422                     player = star_match[0];
3423                 } else {
3424                     player = star_match[2];
3425                 }
3426                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3427                         ics_prefix, StripHighlightAndTitle(player));
3428                 SendToICS(str);
3429
3430                 /* Save ratings from notify string */
3431                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3432                 player1Rating = string_to_rating(star_match[1]);
3433                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3434                 player2Rating = string_to_rating(star_match[3]);
3435
3436                 if (appData.debugMode)
3437                   fprintf(debugFP,
3438                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3439                           player1Name, player1Rating,
3440                           player2Name, player2Rating);
3441
3442                 continue;
3443             }
3444
3445             /* Deal with automatic examine mode after a game,
3446                and with IcsObserving -> IcsExamining transition */
3447             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3448                 looking_at(buf, &i, "has made you an examiner of game *")) {
3449
3450                 int gamenum = atoi(star_match[0]);
3451                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3452                     gamenum == ics_gamenum) {
3453                     /* We were already playing or observing this game;
3454                        no need to refetch history */
3455                     gameMode = IcsExamining;
3456                     if (pausing) {
3457                         pauseExamForwardMostMove = forwardMostMove;
3458                     } else if (currentMove < forwardMostMove) {
3459                         ForwardInner(forwardMostMove);
3460                     }
3461                 } else {
3462                     /* I don't think this case really can happen */
3463                     SendToICS(ics_prefix);
3464                     SendToICS("refresh\n");
3465                 }
3466                 continue;
3467             }
3468
3469             /* Error messages */
3470 //          if (ics_user_moved) {
3471             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3472                 if (looking_at(buf, &i, "Illegal move") ||
3473                     looking_at(buf, &i, "Not a legal move") ||
3474                     looking_at(buf, &i, "Your king is in check") ||
3475                     looking_at(buf, &i, "It isn't your turn") ||
3476                     looking_at(buf, &i, "It is not your move")) {
3477                     /* Illegal move */
3478                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3479                         currentMove = forwardMostMove-1;
3480                         DisplayMove(currentMove - 1); /* before DMError */
3481                         DrawPosition(FALSE, boards[currentMove]);
3482                         SwitchClocks(forwardMostMove-1); // [HGM] race
3483                         DisplayBothClocks();
3484                     }
3485                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3486                     ics_user_moved = 0;
3487                     continue;
3488                 }
3489             }
3490
3491             if (looking_at(buf, &i, "still have time") ||
3492                 looking_at(buf, &i, "not out of time") ||
3493                 looking_at(buf, &i, "either player is out of time") ||
3494                 looking_at(buf, &i, "has timeseal; checking")) {
3495                 /* We must have called his flag a little too soon */
3496                 whiteFlag = blackFlag = FALSE;
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "added * seconds to") ||
3501                 looking_at(buf, &i, "seconds were added to")) {
3502                 /* Update the clocks */
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3509                 ics_clock_paused = TRUE;
3510                 StopClocks();
3511                 continue;
3512             }
3513
3514             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3515                 ics_clock_paused = FALSE;
3516                 StartClocks();
3517                 continue;
3518             }
3519
3520             /* Grab player ratings from the Creating: message.
3521                Note we have to check for the special case when
3522                the ICS inserts things like [white] or [black]. */
3523             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3524                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3525                 /* star_matches:
3526                    0    player 1 name (not necessarily white)
3527                    1    player 1 rating
3528                    2    empty, white, or black (IGNORED)
3529                    3    player 2 name (not necessarily black)
3530                    4    player 2 rating
3531
3532                    The names/ratings are sorted out when the game
3533                    actually starts (below).
3534                 */
3535                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3536                 player1Rating = string_to_rating(star_match[1]);
3537                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3538                 player2Rating = string_to_rating(star_match[4]);
3539
3540                 if (appData.debugMode)
3541                   fprintf(debugFP,
3542                           "Ratings from 'Creating:' %s %d, %s %d\n",
3543                           player1Name, player1Rating,
3544                           player2Name, player2Rating);
3545
3546                 continue;
3547             }
3548
3549             /* Improved generic start/end-of-game messages */
3550             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3551                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3552                 /* If tkind == 0: */
3553                 /* star_match[0] is the game number */
3554                 /*           [1] is the white player's name */
3555                 /*           [2] is the black player's name */
3556                 /* For end-of-game: */
3557                 /*           [3] is the reason for the game end */
3558                 /*           [4] is a PGN end game-token, preceded by " " */
3559                 /* For start-of-game: */
3560                 /*           [3] begins with "Creating" or "Continuing" */
3561                 /*           [4] is " *" or empty (don't care). */
3562                 int gamenum = atoi(star_match[0]);
3563                 char *whitename, *blackname, *why, *endtoken;
3564                 ChessMove endtype = EndOfFile;
3565
3566                 if (tkind == 0) {
3567                   whitename = star_match[1];
3568                   blackname = star_match[2];
3569                   why = star_match[3];
3570                   endtoken = star_match[4];
3571                 } else {
3572                   whitename = star_match[1];
3573                   blackname = star_match[3];
3574                   why = star_match[5];
3575                   endtoken = star_match[6];
3576                 }
3577
3578                 /* Game start messages */
3579                 if (strncmp(why, "Creating ", 9) == 0 ||
3580                     strncmp(why, "Continuing ", 11) == 0) {
3581                     gs_gamenum = gamenum;
3582                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3583                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3584 #if ZIPPY
3585                     if (appData.zippyPlay) {
3586                         ZippyGameStart(whitename, blackname);
3587                     }
3588 #endif /*ZIPPY*/
3589                     partnerBoardValid = FALSE; // [HGM] bughouse
3590                     continue;
3591                 }
3592
3593                 /* Game end messages */
3594                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3595                     ics_gamenum != gamenum) {
3596                     continue;
3597                 }
3598                 while (endtoken[0] == ' ') endtoken++;
3599                 switch (endtoken[0]) {
3600                   case '*':
3601                   default:
3602                     endtype = GameUnfinished;
3603                     break;
3604                   case '0':
3605                     endtype = BlackWins;
3606                     break;
3607                   case '1':
3608                     if (endtoken[1] == '/')
3609                       endtype = GameIsDrawn;
3610                     else
3611                       endtype = WhiteWins;
3612                     break;
3613                 }
3614                 GameEnds(endtype, why, GE_ICS);
3615 #if ZIPPY
3616                 if (appData.zippyPlay && first.initDone) {
3617                     ZippyGameEnd(endtype, why);
3618                     if (first.pr == NULL) {
3619                       /* Start the next process early so that we'll
3620                          be ready for the next challenge */
3621                       StartChessProgram(&first);
3622                     }
3623                     /* Send "new" early, in case this command takes
3624                        a long time to finish, so that we'll be ready
3625                        for the next challenge. */
3626                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3627                     Reset(TRUE, TRUE);
3628                 }
3629 #endif /*ZIPPY*/
3630                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "Removing game * from observation") ||
3635                 looking_at(buf, &i, "no longer observing game *") ||
3636                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3637                 if (gameMode == IcsObserving &&
3638                     atoi(star_match[0]) == ics_gamenum)
3639                   {
3640                       /* icsEngineAnalyze */
3641                       if (appData.icsEngineAnalyze) {
3642                             ExitAnalyzeMode();
3643                             ModeHighlight();
3644                       }
3645                       StopClocks();
3646                       gameMode = IcsIdle;
3647                       ics_gamenum = -1;
3648                       ics_user_moved = FALSE;
3649                   }
3650                 continue;
3651             }
3652
3653             if (looking_at(buf, &i, "no longer examining game *")) {
3654                 if (gameMode == IcsExamining &&
3655                     atoi(star_match[0]) == ics_gamenum)
3656                   {
3657                       gameMode = IcsIdle;
3658                       ics_gamenum = -1;
3659                       ics_user_moved = FALSE;
3660                   }
3661                 continue;
3662             }
3663
3664             /* Advance leftover_start past any newlines we find,
3665                so only partial lines can get reparsed */
3666             if (looking_at(buf, &i, "\n")) {
3667                 prevColor = curColor;
3668                 if (curColor != ColorNormal) {
3669                     if (oldi > next_out) {
3670                         SendToPlayer(&buf[next_out], oldi - next_out);
3671                         next_out = oldi;
3672                     }
3673                     Colorize(ColorNormal, FALSE);
3674                     curColor = ColorNormal;
3675                 }
3676                 if (started == STARTED_BOARD) {
3677                     started = STARTED_NONE;
3678                     parse[parse_pos] = NULLCHAR;
3679                     ParseBoard12(parse);
3680                     ics_user_moved = 0;
3681
3682                     /* Send premove here */
3683                     if (appData.premove) {
3684                       char str[MSG_SIZ];
3685                       if (currentMove == 0 &&
3686                           gameMode == IcsPlayingWhite &&
3687                           appData.premoveWhite) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (currentMove == 1 &&
3693                                  gameMode == IcsPlayingBlack &&
3694                                  appData.premoveBlack) {
3695                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3696                         if (appData.debugMode)
3697                           fprintf(debugFP, "Sending premove:\n");
3698                         SendToICS(str);
3699                       } else if (gotPremove) {
3700                         gotPremove = 0;
3701                         ClearPremoveHighlights();
3702                         if (appData.debugMode)
3703                           fprintf(debugFP, "Sending premove:\n");
3704                           UserMoveEvent(premoveFromX, premoveFromY,
3705                                         premoveToX, premoveToY,
3706                                         premovePromoChar);
3707                       }
3708                     }
3709
3710                     /* Usually suppress following prompt */
3711                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3712                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3713                         if (looking_at(buf, &i, "*% ")) {
3714                             savingComment = FALSE;
3715                             suppressKibitz = 0;
3716                         }
3717                     }
3718                     next_out = i;
3719                 } else if (started == STARTED_HOLDINGS) {
3720                     int gamenum;
3721                     char new_piece[MSG_SIZ];
3722                     started = STARTED_NONE;
3723                     parse[parse_pos] = NULLCHAR;
3724                     if (appData.debugMode)
3725                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3726                                                         parse, currentMove);
3727                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3728                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3729                         if (gameInfo.variant == VariantNormal) {
3730                           /* [HGM] We seem to switch variant during a game!
3731                            * Presumably no holdings were displayed, so we have
3732                            * to move the position two files to the right to
3733                            * create room for them!
3734                            */
3735                           VariantClass newVariant;
3736                           switch(gameInfo.boardWidth) { // base guess on board width
3737                                 case 9:  newVariant = VariantShogi; break;
3738                                 case 10: newVariant = VariantGreat; break;
3739                                 default: newVariant = VariantCrazyhouse; break;
3740                           }
3741                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3742                           /* Get a move list just to see the header, which
3743                              will tell us whether this is really bug or zh */
3744                           if (ics_getting_history == H_FALSE) {
3745                             ics_getting_history = H_REQUESTED;
3746                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3747                             SendToICS(str);
3748                           }
3749                         }
3750                         new_piece[0] = NULLCHAR;
3751                         sscanf(parse, "game %d white [%s black [%s <- %s",
3752                                &gamenum, white_holding, black_holding,
3753                                new_piece);
3754                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3755                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3756                         /* [HGM] copy holdings to board holdings area */
3757                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3758                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3759                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3760 #if ZIPPY
3761                         if (appData.zippyPlay && first.initDone) {
3762                             ZippyHoldings(white_holding, black_holding,
3763                                           new_piece);
3764                         }
3765 #endif /*ZIPPY*/
3766                         if (tinyLayout || smallLayout) {
3767                             char wh[16], bh[16];
3768                             PackHolding(wh, white_holding);
3769                             PackHolding(bh, black_holding);
3770                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3771                                     gameInfo.white, gameInfo.black);
3772                         } else {
3773                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3774                                     gameInfo.white, white_holding,
3775                                     gameInfo.black, black_holding);
3776                         }
3777                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3778                         DrawPosition(FALSE, boards[currentMove]);
3779                         DisplayTitle(str);
3780                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3781                         sscanf(parse, "game %d white [%s black [%s <- %s",
3782                                &gamenum, white_holding, black_holding,
3783                                new_piece);
3784                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3785                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3786                         /* [HGM] copy holdings to partner-board holdings area */
3787                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3788                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3789                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3790                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3791                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3792                       }
3793                     }
3794                     /* Suppress following prompt */
3795                     if (looking_at(buf, &i, "*% ")) {
3796                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3797                         savingComment = FALSE;
3798                         suppressKibitz = 0;
3799                     }
3800                     next_out = i;
3801                 }
3802                 continue;
3803             }
3804
3805             i++;                /* skip unparsed character and loop back */
3806         }
3807
3808         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3809 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3810 //          SendToPlayer(&buf[next_out], i - next_out);
3811             started != STARTED_HOLDINGS && leftover_start > next_out) {
3812             SendToPlayer(&buf[next_out], leftover_start - next_out);
3813             next_out = i;
3814         }
3815
3816         leftover_len = buf_len - leftover_start;
3817         /* if buffer ends with something we couldn't parse,
3818            reparse it after appending the next read */
3819
3820     } else if (count == 0) {
3821         RemoveInputSource(isr);
3822         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3823     } else {
3824         DisplayFatalError(_("Error reading from ICS"), error, 1);
3825     }
3826 }
3827
3828
3829 /* Board style 12 looks like this:
3830
3831    <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
3832
3833  * The "<12> " is stripped before it gets to this routine.  The two
3834  * trailing 0's (flip state and clock ticking) are later addition, and
3835  * some chess servers may not have them, or may have only the first.
3836  * Additional trailing fields may be added in the future.
3837  */
3838
3839 #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"
3840
3841 #define RELATION_OBSERVING_PLAYED    0
3842 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3843 #define RELATION_PLAYING_MYMOVE      1
3844 #define RELATION_PLAYING_NOTMYMOVE  -1
3845 #define RELATION_EXAMINING           2
3846 #define RELATION_ISOLATED_BOARD     -3
3847 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3848
3849 void
3850 ParseBoard12(string)
3851      char *string;
3852 {
3853     GameMode newGameMode;
3854     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3855     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3856     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3857     char to_play, board_chars[200];
3858     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3859     char black[32], white[32];
3860     Board board;
3861     int prevMove = currentMove;
3862     int ticking = 2;
3863     ChessMove moveType;
3864     int fromX, fromY, toX, toY;
3865     char promoChar;
3866     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3867     char *bookHit = NULL; // [HGM] book
3868     Boolean weird = FALSE, reqFlag = FALSE;
3869
3870     fromX = fromY = toX = toY = -1;
3871
3872     newGame = FALSE;
3873
3874     if (appData.debugMode)
3875       fprintf(debugFP, _("Parsing board: %s\n"), string);
3876
3877     move_str[0] = NULLCHAR;
3878     elapsed_time[0] = NULLCHAR;
3879     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3880         int  i = 0, j;
3881         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3882             if(string[i] == ' ') { ranks++; files = 0; }
3883             else files++;
3884             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3885             i++;
3886         }
3887         for(j = 0; j <i; j++) board_chars[j] = string[j];
3888         board_chars[i] = '\0';
3889         string += i + 1;
3890     }
3891     n = sscanf(string, PATTERN, &to_play, &double_push,
3892                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3893                &gamenum, white, black, &relation, &basetime, &increment,
3894                &white_stren, &black_stren, &white_time, &black_time,
3895                &moveNum, str, elapsed_time, move_str, &ics_flip,
3896                &ticking);
3897
3898     if (n < 21) {
3899         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3900         DisplayError(str, 0);
3901         return;
3902     }
3903
3904     /* Convert the move number to internal form */
3905     moveNum = (moveNum - 1) * 2;
3906     if (to_play == 'B') moveNum++;
3907     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3908       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3909                         0, 1);
3910       return;
3911     }
3912
3913     switch (relation) {
3914       case RELATION_OBSERVING_PLAYED:
3915       case RELATION_OBSERVING_STATIC:
3916         if (gamenum == -1) {
3917             /* Old ICC buglet */
3918             relation = RELATION_OBSERVING_STATIC;
3919         }
3920         newGameMode = IcsObserving;
3921         break;
3922       case RELATION_PLAYING_MYMOVE:
3923       case RELATION_PLAYING_NOTMYMOVE:
3924         newGameMode =
3925           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3926             IcsPlayingWhite : IcsPlayingBlack;
3927         break;
3928       case RELATION_EXAMINING:
3929         newGameMode = IcsExamining;
3930         break;
3931       case RELATION_ISOLATED_BOARD:
3932       default:
3933         /* Just display this board.  If user was doing something else,
3934            we will forget about it until the next board comes. */
3935         newGameMode = IcsIdle;
3936         break;
3937       case RELATION_STARTING_POSITION:
3938         newGameMode = gameMode;
3939         break;
3940     }
3941
3942     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3943          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3944       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3945       char *toSqr;
3946       for (k = 0; k < ranks; k++) {
3947         for (j = 0; j < files; j++)
3948           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3949         if(gameInfo.holdingsWidth > 1) {
3950              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3951              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3952         }
3953       }
3954       CopyBoard(partnerBoard, board);
3955       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3956         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3957         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3958       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3959       if(toSqr = strchr(str, '-')) {
3960         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3961         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3962       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3963       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3964       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3965       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3966       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3967       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3968                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3969       DisplayMessage(partnerStatus, "");
3970         partnerBoardValid = TRUE;
3971       return;
3972     }
3973
3974     /* Modify behavior for initial board display on move listing
3975        of wild games.
3976        */
3977     switch (ics_getting_history) {
3978       case H_FALSE:
3979       case H_REQUESTED:
3980         break;
3981       case H_GOT_REQ_HEADER:
3982       case H_GOT_UNREQ_HEADER:
3983         /* This is the initial position of the current game */
3984         gamenum = ics_gamenum;
3985         moveNum = 0;            /* old ICS bug workaround */
3986         if (to_play == 'B') {
3987           startedFromSetupPosition = TRUE;
3988           blackPlaysFirst = TRUE;
3989           moveNum = 1;
3990           if (forwardMostMove == 0) forwardMostMove = 1;
3991           if (backwardMostMove == 0) backwardMostMove = 1;
3992           if (currentMove == 0) currentMove = 1;
3993         }
3994         newGameMode = gameMode;
3995         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3996         break;
3997       case H_GOT_UNWANTED_HEADER:
3998         /* This is an initial board that we don't want */
3999         return;
4000       case H_GETTING_MOVES:
4001         /* Should not happen */
4002         DisplayError(_("Error gathering move list: extra board"), 0);
4003         ics_getting_history = H_FALSE;
4004         return;
4005     }
4006
4007    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4008                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4009      /* [HGM] We seem to have switched variant unexpectedly
4010       * Try to guess new variant from board size
4011       */
4012           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4013           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4014           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4015           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4016           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4017           if(!weird) newVariant = VariantNormal;
4018           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4019           /* Get a move list just to see the header, which
4020              will tell us whether this is really bug or zh */
4021           if (ics_getting_history == H_FALSE) {
4022             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4023             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4024             SendToICS(str);
4025           }
4026     }
4027
4028     /* Take action if this is the first board of a new game, or of a
4029        different game than is currently being displayed.  */
4030     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4031         relation == RELATION_ISOLATED_BOARD) {
4032
4033         /* Forget the old game and get the history (if any) of the new one */
4034         if (gameMode != BeginningOfGame) {
4035           Reset(TRUE, TRUE);
4036         }
4037         newGame = TRUE;
4038         if (appData.autoRaiseBoard) BoardToTop();
4039         prevMove = -3;
4040         if (gamenum == -1) {
4041             newGameMode = IcsIdle;
4042         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4043                    appData.getMoveList && !reqFlag) {
4044             /* Need to get game history */
4045             ics_getting_history = H_REQUESTED;
4046             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4047             SendToICS(str);
4048         }
4049
4050         /* Initially flip the board to have black on the bottom if playing
4051            black or if the ICS flip flag is set, but let the user change
4052            it with the Flip View button. */
4053         flipView = appData.autoFlipView ?
4054           (newGameMode == IcsPlayingBlack) || ics_flip :
4055           appData.flipView;
4056
4057         /* Done with values from previous mode; copy in new ones */
4058         gameMode = newGameMode;
4059         ModeHighlight();
4060         ics_gamenum = gamenum;
4061         if (gamenum == gs_gamenum) {
4062             int klen = strlen(gs_kind);
4063             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4064             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4065             gameInfo.event = StrSave(str);
4066         } else {
4067             gameInfo.event = StrSave("ICS game");
4068         }
4069         gameInfo.site = StrSave(appData.icsHost);
4070         gameInfo.date = PGNDate();
4071         gameInfo.round = StrSave("-");
4072         gameInfo.white = StrSave(white);
4073         gameInfo.black = StrSave(black);
4074         timeControl = basetime * 60 * 1000;
4075         timeControl_2 = 0;
4076         timeIncrement = increment * 1000;
4077         movesPerSession = 0;
4078         gameInfo.timeControl = TimeControlTagValue();
4079         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4080   if (appData.debugMode) {
4081     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4082     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4083     setbuf(debugFP, NULL);
4084   }
4085
4086         gameInfo.outOfBook = NULL;
4087
4088         /* Do we have the ratings? */
4089         if (strcmp(player1Name, white) == 0 &&
4090             strcmp(player2Name, black) == 0) {
4091             if (appData.debugMode)
4092               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093                       player1Rating, player2Rating);
4094             gameInfo.whiteRating = player1Rating;
4095             gameInfo.blackRating = player2Rating;
4096         } else if (strcmp(player2Name, white) == 0 &&
4097                    strcmp(player1Name, black) == 0) {
4098             if (appData.debugMode)
4099               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4100                       player2Rating, player1Rating);
4101             gameInfo.whiteRating = player2Rating;
4102             gameInfo.blackRating = player1Rating;
4103         }
4104         player1Name[0] = player2Name[0] = NULLCHAR;
4105
4106         /* Silence shouts if requested */
4107         if (appData.quietPlay &&
4108             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4109             SendToICS(ics_prefix);
4110             SendToICS("set shout 0\n");
4111         }
4112     }
4113
4114     /* Deal with midgame name changes */
4115     if (!newGame) {
4116         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4117             if (gameInfo.white) free(gameInfo.white);
4118             gameInfo.white = StrSave(white);
4119         }
4120         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4121             if (gameInfo.black) free(gameInfo.black);
4122             gameInfo.black = StrSave(black);
4123         }
4124     }
4125
4126     /* Throw away game result if anything actually changes in examine mode */
4127     if (gameMode == IcsExamining && !newGame) {
4128         gameInfo.result = GameUnfinished;
4129         if (gameInfo.resultDetails != NULL) {
4130             free(gameInfo.resultDetails);
4131             gameInfo.resultDetails = NULL;
4132         }
4133     }
4134
4135     /* In pausing && IcsExamining mode, we ignore boards coming
4136        in if they are in a different variation than we are. */
4137     if (pauseExamInvalid) return;
4138     if (pausing && gameMode == IcsExamining) {
4139         if (moveNum <= pauseExamForwardMostMove) {
4140             pauseExamInvalid = TRUE;
4141             forwardMostMove = pauseExamForwardMostMove;
4142             return;
4143         }
4144     }
4145
4146   if (appData.debugMode) {
4147     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4148   }
4149     /* Parse the board */
4150     for (k = 0; k < ranks; k++) {
4151       for (j = 0; j < files; j++)
4152         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4153       if(gameInfo.holdingsWidth > 1) {
4154            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4155            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4156       }
4157     }
4158     CopyBoard(boards[moveNum], board);
4159     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4160     if (moveNum == 0) {
4161         startedFromSetupPosition =
4162           !CompareBoards(board, initialPosition);
4163         if(startedFromSetupPosition)
4164             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4165     }
4166
4167     /* [HGM] Set castling rights. Take the outermost Rooks,
4168        to make it also work for FRC opening positions. Note that board12
4169        is really defective for later FRC positions, as it has no way to
4170        indicate which Rook can castle if they are on the same side of King.
4171        For the initial position we grant rights to the outermost Rooks,
4172        and remember thos rights, and we then copy them on positions
4173        later in an FRC game. This means WB might not recognize castlings with
4174        Rooks that have moved back to their original position as illegal,
4175        but in ICS mode that is not its job anyway.
4176     */
4177     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4178     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4179
4180         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4181             if(board[0][i] == WhiteRook) j = i;
4182         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4183         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4187             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4188         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192
4193         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4194         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4195             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4196         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4197             if(board[BOARD_HEIGHT-1][k] == bKing)
4198                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4199         if(gameInfo.variant == VariantTwoKings) {
4200             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4201             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4202             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4203         }
4204     } else { int r;
4205         r = boards[moveNum][CASTLING][0] = initialRights[0];
4206         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4207         r = boards[moveNum][CASTLING][1] = initialRights[1];
4208         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4209         r = boards[moveNum][CASTLING][3] = initialRights[3];
4210         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4211         r = boards[moveNum][CASTLING][4] = initialRights[4];
4212         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4213         /* wildcastle kludge: always assume King has rights */
4214         r = boards[moveNum][CASTLING][2] = initialRights[2];
4215         r = boards[moveNum][CASTLING][5] = initialRights[5];
4216     }
4217     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4218     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4219
4220
4221     if (ics_getting_history == H_GOT_REQ_HEADER ||
4222         ics_getting_history == H_GOT_UNREQ_HEADER) {
4223         /* This was an initial position from a move list, not
4224            the current position */
4225         return;
4226     }
4227
4228     /* Update currentMove and known move number limits */
4229     newMove = newGame || moveNum > forwardMostMove;
4230
4231     if (newGame) {
4232         forwardMostMove = backwardMostMove = currentMove = moveNum;
4233         if (gameMode == IcsExamining && moveNum == 0) {
4234           /* Workaround for ICS limitation: we are not told the wild
4235              type when starting to examine a game.  But if we ask for
4236              the move list, the move list header will tell us */
4237             ics_getting_history = H_REQUESTED;
4238             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239             SendToICS(str);
4240         }
4241     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4242                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4243 #if ZIPPY
4244         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4245         /* [HGM] applied this also to an engine that is silently watching        */
4246         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4247             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4248             gameInfo.variant == currentlyInitializedVariant) {
4249           takeback = forwardMostMove - moveNum;
4250           for (i = 0; i < takeback; i++) {
4251             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4252             SendToProgram("undo\n", &first);
4253           }
4254         }
4255 #endif
4256
4257         forwardMostMove = moveNum;
4258         if (!pausing || currentMove > forwardMostMove)
4259           currentMove = forwardMostMove;
4260     } else {
4261         /* New part of history that is not contiguous with old part */
4262         if (pausing && gameMode == IcsExamining) {
4263             pauseExamInvalid = TRUE;
4264             forwardMostMove = pauseExamForwardMostMove;
4265             return;
4266         }
4267         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4268 #if ZIPPY
4269             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4270                 // [HGM] when we will receive the move list we now request, it will be
4271                 // fed to the engine from the first move on. So if the engine is not
4272                 // in the initial position now, bring it there.
4273                 InitChessProgram(&first, 0);
4274             }
4275 #endif
4276             ics_getting_history = H_REQUESTED;
4277             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4278             SendToICS(str);
4279         }
4280         forwardMostMove = backwardMostMove = currentMove = moveNum;
4281     }
4282
4283     /* Update the clocks */
4284     if (strchr(elapsed_time, '.')) {
4285       /* Time is in ms */
4286       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4287       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4288     } else {
4289       /* Time is in seconds */
4290       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4291       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4292     }
4293
4294
4295 #if ZIPPY
4296     if (appData.zippyPlay && newGame &&
4297         gameMode != IcsObserving && gameMode != IcsIdle &&
4298         gameMode != IcsExamining)
4299       ZippyFirstBoard(moveNum, basetime, increment);
4300 #endif
4301
4302     /* Put the move on the move list, first converting
4303        to canonical algebraic form. */
4304     if (moveNum > 0) {
4305   if (appData.debugMode) {
4306     if (appData.debugMode) { int f = forwardMostMove;
4307         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4308                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4309                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4310     }
4311     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4312     fprintf(debugFP, "moveNum = %d\n", moveNum);
4313     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4314     setbuf(debugFP, NULL);
4315   }
4316         if (moveNum <= backwardMostMove) {
4317             /* We don't know what the board looked like before
4318                this move.  Punt. */
4319           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4320             strcat(parseList[moveNum - 1], " ");
4321             strcat(parseList[moveNum - 1], elapsed_time);
4322             moveList[moveNum - 1][0] = NULLCHAR;
4323         } else if (strcmp(move_str, "none") == 0) {
4324             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4325             /* Again, we don't know what the board looked like;
4326                this is really the start of the game. */
4327             parseList[moveNum - 1][0] = NULLCHAR;
4328             moveList[moveNum - 1][0] = NULLCHAR;
4329             backwardMostMove = moveNum;
4330             startedFromSetupPosition = TRUE;
4331             fromX = fromY = toX = toY = -1;
4332         } else {
4333           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4334           //                 So we parse the long-algebraic move string in stead of the SAN move
4335           int valid; char buf[MSG_SIZ], *prom;
4336
4337           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4338                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4339           // str looks something like "Q/a1-a2"; kill the slash
4340           if(str[1] == '/')
4341             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4342           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4343           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4344                 strcat(buf, prom); // long move lacks promo specification!
4345           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4346                 if(appData.debugMode)
4347                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4348                 safeStrCpy(move_str, buf, MSG_SIZ);
4349           }
4350           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4351                                 &fromX, &fromY, &toX, &toY, &promoChar)
4352                || ParseOneMove(buf, moveNum - 1, &moveType,
4353                                 &fromX, &fromY, &toX, &toY, &promoChar);
4354           // end of long SAN patch
4355           if (valid) {
4356             (void) CoordsToAlgebraic(boards[moveNum - 1],
4357                                      PosFlags(moveNum - 1),
4358                                      fromY, fromX, toY, toX, promoChar,
4359                                      parseList[moveNum-1]);
4360             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4361               case MT_NONE:
4362               case MT_STALEMATE:
4363               default:
4364                 break;
4365               case MT_CHECK:
4366                 if(gameInfo.variant != VariantShogi)
4367                     strcat(parseList[moveNum - 1], "+");
4368                 break;
4369               case MT_CHECKMATE:
4370               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4371                 strcat(parseList[moveNum - 1], "#");
4372                 break;
4373             }
4374             strcat(parseList[moveNum - 1], " ");
4375             strcat(parseList[moveNum - 1], elapsed_time);
4376             /* currentMoveString is set as a side-effect of ParseOneMove */
4377             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4378             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4379             strcat(moveList[moveNum - 1], "\n");
4380
4381             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4382                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4383               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4384                 ChessSquare old, new = boards[moveNum][k][j];
4385                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4386                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4387                   if(old == new) continue;
4388                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4389                   else if(new == WhiteWazir || new == BlackWazir) {
4390                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4391                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4392                       else boards[moveNum][k][j] = old; // preserve type of Gold
4393                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4394                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4395               }
4396           } else {
4397             /* Move from ICS was illegal!?  Punt. */
4398             if (appData.debugMode) {
4399               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4400               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4401             }
4402             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4403             strcat(parseList[moveNum - 1], " ");
4404             strcat(parseList[moveNum - 1], elapsed_time);
4405             moveList[moveNum - 1][0] = NULLCHAR;
4406             fromX = fromY = toX = toY = -1;
4407           }
4408         }
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4411     setbuf(debugFP, NULL);
4412   }
4413
4414 #if ZIPPY
4415         /* Send move to chess program (BEFORE animating it). */
4416         if (appData.zippyPlay && !newGame && newMove &&
4417            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4418
4419             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4420                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4421                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4422                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4423                             move_str);
4424                     DisplayError(str, 0);
4425                 } else {
4426                     if (first.sendTime) {
4427                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4428                     }
4429                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4430                     if (firstMove && !bookHit) {
4431                         firstMove = FALSE;
4432                         if (first.useColors) {
4433                           SendToProgram(gameMode == IcsPlayingWhite ?
4434                                         "white\ngo\n" :
4435                                         "black\ngo\n", &first);
4436                         } else {
4437                           SendToProgram("go\n", &first);
4438                         }
4439                         first.maybeThinking = TRUE;
4440                     }
4441                 }
4442             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4443               if (moveList[moveNum - 1][0] == NULLCHAR) {
4444                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4445                 DisplayError(str, 0);
4446               } else {
4447                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4448                 SendMoveToProgram(moveNum - 1, &first);
4449               }
4450             }
4451         }
4452 #endif
4453     }
4454
4455     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4456         /* If move comes from a remote source, animate it.  If it
4457            isn't remote, it will have already been animated. */
4458         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4459             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4460         }
4461         if (!pausing && appData.highlightLastMove) {
4462             SetHighlights(fromX, fromY, toX, toY);
4463         }
4464     }
4465
4466     /* Start the clocks */
4467     whiteFlag = blackFlag = FALSE;
4468     appData.clockMode = !(basetime == 0 && increment == 0);
4469     if (ticking == 0) {
4470       ics_clock_paused = TRUE;
4471       StopClocks();
4472     } else if (ticking == 1) {
4473       ics_clock_paused = FALSE;
4474     }
4475     if (gameMode == IcsIdle ||
4476         relation == RELATION_OBSERVING_STATIC ||
4477         relation == RELATION_EXAMINING ||
4478         ics_clock_paused)
4479       DisplayBothClocks();
4480     else
4481       StartClocks();
4482
4483     /* Display opponents and material strengths */
4484     if (gameInfo.variant != VariantBughouse &&
4485         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4486         if (tinyLayout || smallLayout) {
4487             if(gameInfo.variant == VariantNormal)
4488               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4489                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4490                     basetime, increment);
4491             else
4492               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4493                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4494                     basetime, increment, (int) gameInfo.variant);
4495         } else {
4496             if(gameInfo.variant == VariantNormal)
4497               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4498                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4499                     basetime, increment);
4500             else
4501               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4502                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4503                     basetime, increment, VariantName(gameInfo.variant));
4504         }
4505         DisplayTitle(str);
4506   if (appData.debugMode) {
4507     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4508   }
4509     }
4510
4511
4512     /* Display the board */
4513     if (!pausing && !appData.noGUI) {
4514
4515       if (appData.premove)
4516           if (!gotPremove ||
4517              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4518              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4519               ClearPremoveHighlights();
4520
4521       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4522         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4523       DrawPosition(j, boards[currentMove]);
4524
4525       DisplayMove(moveNum - 1);
4526       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4527             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4528               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4529         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4530       }
4531     }
4532
4533     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4534 #if ZIPPY
4535     if(bookHit) { // [HGM] book: simulate book reply
4536         static char bookMove[MSG_SIZ]; // a bit generous?
4537
4538         programStats.nodes = programStats.depth = programStats.time =
4539         programStats.score = programStats.got_only_move = 0;
4540         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4541
4542         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4543         strcat(bookMove, bookHit);
4544         HandleMachineMove(bookMove, &first);
4545     }
4546 #endif
4547 }
4548
4549 void
4550 GetMoveListEvent()
4551 {
4552     char buf[MSG_SIZ];
4553     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4554         ics_getting_history = H_REQUESTED;
4555         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4556         SendToICS(buf);
4557     }
4558 }
4559
4560 void
4561 AnalysisPeriodicEvent(force)
4562      int force;
4563 {
4564     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4565          && !force) || !appData.periodicUpdates)
4566       return;
4567
4568     /* Send . command to Crafty to collect stats */
4569     SendToProgram(".\n", &first);
4570
4571     /* Don't send another until we get a response (this makes
4572        us stop sending to old Crafty's which don't understand
4573        the "." command (sending illegal cmds resets node count & time,
4574        which looks bad)) */
4575     programStats.ok_to_send = 0;
4576 }
4577
4578 void ics_update_width(new_width)
4579         int new_width;
4580 {
4581         ics_printf("set width %d\n", new_width);
4582 }
4583
4584 void
4585 SendMoveToProgram(moveNum, cps)
4586      int moveNum;
4587      ChessProgramState *cps;
4588 {
4589     char buf[MSG_SIZ];
4590
4591     if (cps->useUsermove) {
4592       SendToProgram("usermove ", cps);
4593     }
4594     if (cps->useSAN) {
4595       char *space;
4596       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4597         int len = space - parseList[moveNum];
4598         memcpy(buf, parseList[moveNum], len);
4599         buf[len++] = '\n';
4600         buf[len] = NULLCHAR;
4601       } else {
4602         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4603       }
4604       SendToProgram(buf, cps);
4605     } else {
4606       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4607         AlphaRank(moveList[moveNum], 4);
4608         SendToProgram(moveList[moveNum], cps);
4609         AlphaRank(moveList[moveNum], 4); // and back
4610       } else
4611       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4612        * the engine. It would be nice to have a better way to identify castle
4613        * moves here. */
4614       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4615                                                                          && cps->useOOCastle) {
4616         int fromX = moveList[moveNum][0] - AAA;
4617         int fromY = moveList[moveNum][1] - ONE;
4618         int toX = moveList[moveNum][2] - AAA;
4619         int toY = moveList[moveNum][3] - ONE;
4620         if((boards[moveNum][fromY][fromX] == WhiteKing
4621             && boards[moveNum][toY][toX] == WhiteRook)
4622            || (boards[moveNum][fromY][fromX] == BlackKing
4623                && boards[moveNum][toY][toX] == BlackRook)) {
4624           if(toX > fromX) SendToProgram("O-O\n", cps);
4625           else SendToProgram("O-O-O\n", cps);
4626         }
4627         else SendToProgram(moveList[moveNum], cps);
4628       }
4629       else SendToProgram(moveList[moveNum], cps);
4630       /* End of additions by Tord */
4631     }
4632
4633     /* [HGM] setting up the opening has brought engine in force mode! */
4634     /*       Send 'go' if we are in a mode where machine should play. */
4635     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4636         (gameMode == TwoMachinesPlay   ||
4637 #if ZIPPY
4638          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4639 #endif
4640          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4641         SendToProgram("go\n", cps);
4642   if (appData.debugMode) {
4643     fprintf(debugFP, "(extra)\n");
4644   }
4645     }
4646     setboardSpoiledMachineBlack = 0;
4647 }
4648
4649 void
4650 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4651      ChessMove moveType;
4652      int fromX, fromY, toX, toY;
4653      char promoChar;
4654 {
4655     char user_move[MSG_SIZ];
4656
4657     switch (moveType) {
4658       default:
4659         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4660                 (int)moveType, fromX, fromY, toX, toY);
4661         DisplayError(user_move + strlen("say "), 0);
4662         break;
4663       case WhiteKingSideCastle:
4664       case BlackKingSideCastle:
4665       case WhiteQueenSideCastleWild:
4666       case BlackQueenSideCastleWild:
4667       /* PUSH Fabien */
4668       case WhiteHSideCastleFR:
4669       case BlackHSideCastleFR:
4670       /* POP Fabien */
4671         snprintf(user_move, MSG_SIZ, "o-o\n");
4672         break;
4673       case WhiteQueenSideCastle:
4674       case BlackQueenSideCastle:
4675       case WhiteKingSideCastleWild:
4676       case BlackKingSideCastleWild:
4677       /* PUSH Fabien */
4678       case WhiteASideCastleFR:
4679       case BlackASideCastleFR:
4680       /* POP Fabien */
4681         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4682         break;
4683       case WhiteNonPromotion:
4684       case BlackNonPromotion:
4685         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4686         break;
4687       case WhitePromotion:
4688       case BlackPromotion:
4689         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4690           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4691                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4692                 PieceToChar(WhiteFerz));
4693         else if(gameInfo.variant == VariantGreat)
4694           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4695                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4696                 PieceToChar(WhiteMan));
4697         else
4698           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4699                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4700                 promoChar);
4701         break;
4702       case WhiteDrop:
4703       case BlackDrop:
4704       drop:
4705         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4706                  ToUpper(PieceToChar((ChessSquare) fromX)),
4707                  AAA + toX, ONE + toY);
4708         break;
4709       case IllegalMove:  /* could be a variant we don't quite understand */
4710         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4711       case NormalMove:
4712       case WhiteCapturesEnPassant:
4713       case BlackCapturesEnPassant:
4714         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4715                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4716         break;
4717     }
4718     SendToICS(user_move);
4719     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4720         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4721 }
4722
4723 void
4724 UploadGameEvent()
4725 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4726     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4727     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4728     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4729         DisplayError("You cannot do this while you are playing or observing", 0);
4730         return;
4731     }
4732     if(gameMode != IcsExamining) { // is this ever not the case?
4733         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4734
4735         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4736           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4737         } else { // on FICS we must first go to general examine mode
4738           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4739         }
4740         if(gameInfo.variant != VariantNormal) {
4741             // try figure out wild number, as xboard names are not always valid on ICS
4742             for(i=1; i<=36; i++) {
4743               snprintf(buf, MSG_SIZ, "wild/%d", i);
4744                 if(StringToVariant(buf) == gameInfo.variant) break;
4745             }
4746             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4747             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4748             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4749         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4750         SendToICS(ics_prefix);
4751         SendToICS(buf);
4752         if(startedFromSetupPosition || backwardMostMove != 0) {
4753           fen = PositionToFEN(backwardMostMove, NULL);
4754           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4755             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4756             SendToICS(buf);
4757           } else { // FICS: everything has to set by separate bsetup commands
4758             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4759             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4760             SendToICS(buf);
4761             if(!WhiteOnMove(backwardMostMove)) {
4762                 SendToICS("bsetup tomove black\n");
4763             }
4764             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4765             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4766             SendToICS(buf);
4767             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4768             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4769             SendToICS(buf);
4770             i = boards[backwardMostMove][EP_STATUS];
4771             if(i >= 0) { // set e.p.
4772               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4773                 SendToICS(buf);
4774             }
4775             bsetup++;
4776           }
4777         }
4778       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4779             SendToICS("bsetup done\n"); // switch to normal examining.
4780     }
4781     for(i = backwardMostMove; i<last; i++) {
4782         char buf[20];
4783         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4784         SendToICS(buf);
4785     }
4786     SendToICS(ics_prefix);
4787     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4788 }
4789
4790 void
4791 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4792      int rf, ff, rt, ft;
4793      char promoChar;
4794      char move[7];
4795 {
4796     if (rf == DROP_RANK) {
4797       sprintf(move, "%c@%c%c\n",
4798                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4799     } else {
4800         if (promoChar == 'x' || promoChar == NULLCHAR) {
4801           sprintf(move, "%c%c%c%c\n",
4802                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4803         } else {
4804             sprintf(move, "%c%c%c%c%c\n",
4805                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4806         }
4807     }
4808 }
4809
4810 void
4811 ProcessICSInitScript(f)
4812      FILE *f;
4813 {
4814     char buf[MSG_SIZ];
4815
4816     while (fgets(buf, MSG_SIZ, f)) {
4817         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4818     }
4819
4820     fclose(f);
4821 }
4822
4823
4824 static int lastX, lastY, selectFlag, dragging;
4825
4826 void
4827 Sweep(int step)
4828 {
4829     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4830     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4831     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4832     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4833     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4834     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4835     do {
4836         promoSweep -= step;
4837         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4838         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4839         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4840         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4841         if(!step) step = 1;
4842     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4843             appData.testLegality && (promoSweep == king ||
4844             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4845     ChangeDragPiece(promoSweep);
4846 }
4847
4848 int PromoScroll(int x, int y)
4849 {
4850   int step = 0;
4851
4852   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4853   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4854   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4855   if(!step) return FALSE;
4856   lastX = x; lastY = y;
4857   if((promoSweep < BlackPawn) == flipView) step = -step;
4858   if(step > 0) selectFlag = 1;
4859   if(!selectFlag) Sweep(step);
4860   return FALSE;
4861 }
4862
4863 void
4864 NextPiece(int step)
4865 {
4866     ChessSquare piece = boards[currentMove][toY][toX];
4867     do {
4868         pieceSweep -= step;
4869         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4870         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4871         if(!step) step = -1;
4872     } while(PieceToChar(pieceSweep) == '.');
4873     boards[currentMove][toY][toX] = pieceSweep;
4874     DrawPosition(FALSE, boards[currentMove]);
4875     boards[currentMove][toY][toX] = piece;
4876 }
4877 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4878 void
4879 AlphaRank(char *move, int n)
4880 {
4881 //    char *p = move, c; int x, y;
4882
4883     if (appData.debugMode) {
4884         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4885     }
4886
4887     if(move[1]=='*' &&
4888        move[2]>='0' && move[2]<='9' &&
4889        move[3]>='a' && move[3]<='x'    ) {
4890         move[1] = '@';
4891         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4892         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4893     } else
4894     if(move[0]>='0' && move[0]<='9' &&
4895        move[1]>='a' && move[1]<='x' &&
4896        move[2]>='0' && move[2]<='9' &&
4897        move[3]>='a' && move[3]<='x'    ) {
4898         /* input move, Shogi -> normal */
4899         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4900         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4901         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4902         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4903     } else
4904     if(move[1]=='@' &&
4905        move[3]>='0' && move[3]<='9' &&
4906        move[2]>='a' && move[2]<='x'    ) {
4907         move[1] = '*';
4908         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4909         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4910     } else
4911     if(
4912        move[0]>='a' && move[0]<='x' &&
4913        move[3]>='0' && move[3]<='9' &&
4914        move[2]>='a' && move[2]<='x'    ) {
4915          /* output move, normal -> Shogi */
4916         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4917         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4918         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4919         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4920         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4921     }
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "   out = '%s'\n", move);
4924     }
4925 }
4926
4927 char yy_textstr[8000];
4928
4929 /* Parser for moves from gnuchess, ICS, or user typein box */
4930 Boolean
4931 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4932      char *move;
4933      int moveNum;
4934      ChessMove *moveType;
4935      int *fromX, *fromY, *toX, *toY;
4936      char *promoChar;
4937 {
4938     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4939
4940     switch (*moveType) {
4941       case WhitePromotion:
4942       case BlackPromotion:
4943       case WhiteNonPromotion:
4944       case BlackNonPromotion:
4945       case NormalMove:
4946       case WhiteCapturesEnPassant:
4947       case BlackCapturesEnPassant:
4948       case WhiteKingSideCastle:
4949       case WhiteQueenSideCastle:
4950       case BlackKingSideCastle:
4951       case BlackQueenSideCastle:
4952       case WhiteKingSideCastleWild:
4953       case WhiteQueenSideCastleWild:
4954       case BlackKingSideCastleWild:
4955       case BlackQueenSideCastleWild:
4956       /* Code added by Tord: */
4957       case WhiteHSideCastleFR:
4958       case WhiteASideCastleFR:
4959       case BlackHSideCastleFR:
4960       case BlackASideCastleFR:
4961       /* End of code added by Tord */
4962       case IllegalMove:         /* bug or odd chess variant */
4963         *fromX = currentMoveString[0] - AAA;
4964         *fromY = currentMoveString[1] - ONE;
4965         *toX = currentMoveString[2] - AAA;
4966         *toY = currentMoveString[3] - ONE;
4967         *promoChar = currentMoveString[4];
4968         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4969             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4970     if (appData.debugMode) {
4971         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4972     }
4973             *fromX = *fromY = *toX = *toY = 0;
4974             return FALSE;
4975         }
4976         if (appData.testLegality) {
4977           return (*moveType != IllegalMove);
4978         } else {
4979           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4980                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4981         }
4982
4983       case WhiteDrop:
4984       case BlackDrop:
4985         *fromX = *moveType == WhiteDrop ?
4986           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4987           (int) CharToPiece(ToLower(currentMoveString[0]));
4988         *fromY = DROP_RANK;
4989         *toX = currentMoveString[2] - AAA;
4990         *toY = currentMoveString[3] - ONE;
4991         *promoChar = NULLCHAR;
4992         return TRUE;
4993
4994       case AmbiguousMove:
4995       case ImpossibleMove:
4996       case EndOfFile:
4997       case ElapsedTime:
4998       case Comment:
4999       case PGNTag:
5000       case NAG:
5001       case WhiteWins:
5002       case BlackWins:
5003       case GameIsDrawn:
5004       default:
5005     if (appData.debugMode) {
5006         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5007     }
5008         /* bug? */
5009         *fromX = *fromY = *toX = *toY = 0;
5010         *promoChar = NULLCHAR;
5011         return FALSE;
5012     }
5013 }
5014
5015
5016 void
5017 ParsePV(char *pv, Boolean storeComments)
5018 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5019   int fromX, fromY, toX, toY; char promoChar;
5020   ChessMove moveType;
5021   Boolean valid;
5022   int nr = 0;
5023
5024   endPV = forwardMostMove;
5025   do {
5026     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5027     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5028     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5029 if(appData.debugMode){
5030 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5031 }
5032     if(!valid && nr == 0 &&
5033        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5034         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5035         // Hande case where played move is different from leading PV move
5036         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5037         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5038         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5039         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5040           endPV += 2; // if position different, keep this
5041           moveList[endPV-1][0] = fromX + AAA;
5042           moveList[endPV-1][1] = fromY + ONE;
5043           moveList[endPV-1][2] = toX + AAA;
5044           moveList[endPV-1][3] = toY + ONE;
5045           parseList[endPV-1][0] = NULLCHAR;
5046           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5047         }
5048       }
5049     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5050     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5051     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5052     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5053         valid++; // allow comments in PV
5054         continue;
5055     }
5056     nr++;
5057     if(endPV+1 > framePtr) break; // no space, truncate
5058     if(!valid) break;
5059     endPV++;
5060     CopyBoard(boards[endPV], boards[endPV-1]);
5061     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5062     moveList[endPV-1][0] = fromX + AAA;
5063     moveList[endPV-1][1] = fromY + ONE;
5064     moveList[endPV-1][2] = toX + AAA;
5065     moveList[endPV-1][3] = toY + ONE;
5066     moveList[endPV-1][4] = promoChar;
5067     moveList[endPV-1][5] = NULLCHAR;
5068     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5069     if(storeComments)
5070         CoordsToAlgebraic(boards[endPV - 1],
5071                              PosFlags(endPV - 1),
5072                              fromY, fromX, toY, toX, promoChar,
5073                              parseList[endPV - 1]);
5074     else
5075         parseList[endPV-1][0] = NULLCHAR;
5076   } while(valid);
5077   currentMove = endPV;
5078   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5079   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5080                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5081   DrawPosition(TRUE, boards[currentMove]);
5082 }
5083
5084 Boolean
5085 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5086 {
5087         int startPV;
5088         char *p;
5089
5090         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5091         lastX = x; lastY = y;
5092         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5093         startPV = index;
5094         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5095         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5096         index = startPV;
5097         do{ while(buf[index] && buf[index] != '\n') index++;
5098         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5099         buf[index] = 0;
5100         ParsePV(buf+startPV, FALSE);
5101         *start = startPV; *end = index-1;
5102         return TRUE;
5103 }
5104
5105 Boolean
5106 LoadPV(int x, int y)
5107 { // called on right mouse click to load PV
5108   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5109   lastX = x; lastY = y;
5110   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5111   return TRUE;
5112 }
5113
5114 void
5115 UnLoadPV()
5116 {
5117   if(endPV < 0) return;
5118   endPV = -1;
5119   currentMove = forwardMostMove;
5120   ClearPremoveHighlights();
5121   DrawPosition(TRUE, boards[currentMove]);
5122 }
5123
5124 void
5125 MovePV(int x, int y, int h)
5126 { // step through PV based on mouse coordinates (called on mouse move)
5127   int margin = h>>3, step = 0;
5128
5129   // we must somehow check if right button is still down (might be released off board!)
5130   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5131   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5132   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5133   if(!step) return;
5134   lastX = x; lastY = y;
5135
5136   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5137   if(endPV < 0) return;
5138   if(y < margin) step = 1; else
5139   if(y > h - margin) step = -1;
5140   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5141   currentMove += step;
5142   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5143   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5144                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5145   DrawPosition(FALSE, boards[currentMove]);
5146 }
5147
5148
5149 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5150 // All positions will have equal probability, but the current method will not provide a unique
5151 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5152 #define DARK 1
5153 #define LITE 2
5154 #define ANY 3
5155
5156 int squaresLeft[4];
5157 int piecesLeft[(int)BlackPawn];
5158 int seed, nrOfShuffles;
5159
5160 void GetPositionNumber()
5161 {       // sets global variable seed
5162         int i;
5163
5164         seed = appData.defaultFrcPosition;
5165         if(seed < 0) { // randomize based on time for negative FRC position numbers
5166                 for(i=0; i<50; i++) seed += random();
5167                 seed = random() ^ random() >> 8 ^ random() << 8;
5168                 if(seed<0) seed = -seed;
5169         }
5170 }
5171
5172 int put(Board board, int pieceType, int rank, int n, int shade)
5173 // put the piece on the (n-1)-th empty squares of the given shade
5174 {
5175         int i;
5176
5177         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5178                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5179                         board[rank][i] = (ChessSquare) pieceType;
5180                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5181                         squaresLeft[ANY]--;
5182                         piecesLeft[pieceType]--;
5183                         return i;
5184                 }
5185         }
5186         return -1;
5187 }
5188
5189
5190 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5191 // calculate where the next piece goes, (any empty square), and put it there
5192 {
5193         int i;
5194
5195         i = seed % squaresLeft[shade];
5196         nrOfShuffles *= squaresLeft[shade];
5197         seed /= squaresLeft[shade];
5198         put(board, pieceType, rank, i, shade);
5199 }
5200
5201 void AddTwoPieces(Board board, int pieceType, int rank)
5202 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5203 {
5204         int i, n=squaresLeft[ANY], j=n-1, k;
5205
5206         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5207         i = seed % k;  // pick one
5208         nrOfShuffles *= k;
5209         seed /= k;
5210         while(i >= j) i -= j--;
5211         j = n - 1 - j; i += j;
5212         put(board, pieceType, rank, j, ANY);
5213         put(board, pieceType, rank, i, ANY);
5214 }
5215
5216 void SetUpShuffle(Board board, int number)
5217 {
5218         int i, p, first=1;
5219
5220         GetPositionNumber(); nrOfShuffles = 1;
5221
5222         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5223         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5224         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5225
5226         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5227
5228         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5229             p = (int) board[0][i];
5230             if(p < (int) BlackPawn) piecesLeft[p] ++;
5231             board[0][i] = EmptySquare;
5232         }
5233
5234         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5235             // shuffles restricted to allow normal castling put KRR first
5236             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5237                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5238             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5239                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5240             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5241                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5242             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5243                 put(board, WhiteRook, 0, 0, ANY);
5244             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5245         }
5246
5247         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5248             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5249             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5250                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5251                 while(piecesLeft[p] >= 2) {
5252                     AddOnePiece(board, p, 0, LITE);
5253                     AddOnePiece(board, p, 0, DARK);
5254                 }
5255                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5256             }
5257
5258         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5259             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5260             // but we leave King and Rooks for last, to possibly obey FRC restriction
5261             if(p == (int)WhiteRook) continue;
5262             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5263             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5264         }
5265
5266         // now everything is placed, except perhaps King (Unicorn) and Rooks
5267
5268         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5269             // Last King gets castling rights
5270             while(piecesLeft[(int)WhiteUnicorn]) {
5271                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5272                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5273             }
5274
5275             while(piecesLeft[(int)WhiteKing]) {
5276                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5277                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5278             }
5279
5280
5281         } else {
5282             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5283             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5284         }
5285
5286         // Only Rooks can be left; simply place them all
5287         while(piecesLeft[(int)WhiteRook]) {
5288                 i = put(board, WhiteRook, 0, 0, ANY);
5289                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5290                         if(first) {
5291                                 first=0;
5292                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5293                         }
5294                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5295                 }
5296         }
5297         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5298             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5299         }
5300
5301         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5302 }
5303
5304 int SetCharTable( char *table, const char * map )
5305 /* [HGM] moved here from winboard.c because of its general usefulness */
5306 /*       Basically a safe strcpy that uses the last character as King */
5307 {
5308     int result = FALSE; int NrPieces;
5309
5310     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5311                     && NrPieces >= 12 && !(NrPieces&1)) {
5312         int i; /* [HGM] Accept even length from 12 to 34 */
5313
5314         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5315         for( i=0; i<NrPieces/2-1; i++ ) {
5316             table[i] = map[i];
5317             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5318         }
5319         table[(int) WhiteKing]  = map[NrPieces/2-1];
5320         table[(int) BlackKing]  = map[NrPieces-1];
5321
5322         result = TRUE;
5323     }
5324
5325     return result;
5326 }
5327
5328 void Prelude(Board board)
5329 {       // [HGM] superchess: random selection of exo-pieces
5330         int i, j, k; ChessSquare p;
5331         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5332
5333         GetPositionNumber(); // use FRC position number
5334
5335         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5336             SetCharTable(pieceToChar, appData.pieceToCharTable);
5337             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5338                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5339         }
5340
5341         j = seed%4;                 seed /= 4;
5342         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5343         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5344         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5345         j = seed%3 + (seed%3 >= j); seed /= 3;
5346         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5347         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5348         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5349         j = seed%3;                 seed /= 3;
5350         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5351         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5352         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5353         j = seed%2 + (seed%2 >= j); seed /= 2;
5354         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5355         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5356         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5357         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5358         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5359         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5360         put(board, exoPieces[0],    0, 0, ANY);
5361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5362 }
5363
5364 void
5365 InitPosition(redraw)
5366      int redraw;
5367 {
5368     ChessSquare (* pieces)[BOARD_FILES];
5369     int i, j, pawnRow, overrule,
5370     oldx = gameInfo.boardWidth,
5371     oldy = gameInfo.boardHeight,
5372     oldh = gameInfo.holdingsWidth;
5373     static int oldv;
5374
5375     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5376
5377     /* [AS] Initialize pv info list [HGM] and game status */
5378     {
5379         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5380             pvInfoList[i].depth = 0;
5381             boards[i][EP_STATUS] = EP_NONE;
5382             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5383         }
5384
5385         initialRulePlies = 0; /* 50-move counter start */
5386
5387         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5388         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5389     }
5390
5391
5392     /* [HGM] logic here is completely changed. In stead of full positions */
5393     /* the initialized data only consist of the two backranks. The switch */
5394     /* selects which one we will use, which is than copied to the Board   */
5395     /* initialPosition, which for the rest is initialized by Pawns and    */
5396     /* empty squares. This initial position is then copied to boards[0],  */
5397     /* possibly after shuffling, so that it remains available.            */
5398
5399     gameInfo.holdingsWidth = 0; /* default board sizes */
5400     gameInfo.boardWidth    = 8;
5401     gameInfo.boardHeight   = 8;
5402     gameInfo.holdingsSize  = 0;
5403     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5404     for(i=0; i<BOARD_FILES-2; i++)
5405       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5406     initialPosition[EP_STATUS] = EP_NONE;
5407     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5408     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5409          SetCharTable(pieceNickName, appData.pieceNickNames);
5410     else SetCharTable(pieceNickName, "............");
5411     pieces = FIDEArray;
5412
5413     switch (gameInfo.variant) {
5414     case VariantFischeRandom:
5415       shuffleOpenings = TRUE;
5416     default:
5417       break;
5418     case VariantShatranj:
5419       pieces = ShatranjArray;
5420       nrCastlingRights = 0;
5421       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5422       break;
5423     case VariantMakruk:
5424       pieces = makrukArray;
5425       nrCastlingRights = 0;
5426       startedFromSetupPosition = TRUE;
5427       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5428       break;
5429     case VariantTwoKings:
5430       pieces = twoKingsArray;
5431       break;
5432     case VariantCapaRandom:
5433       shuffleOpenings = TRUE;
5434     case VariantCapablanca:
5435       pieces = CapablancaArray;
5436       gameInfo.boardWidth = 10;
5437       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5438       break;
5439     case VariantGothic:
5440       pieces = GothicArray;
5441       gameInfo.boardWidth = 10;
5442       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5443       break;
5444     case VariantSChess:
5445       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5446       gameInfo.holdingsSize = 7;
5447       break;
5448     case VariantJanus:
5449       pieces = JanusArray;
5450       gameInfo.boardWidth = 10;
5451       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5452       nrCastlingRights = 6;
5453         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5454         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5455         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5456         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5457         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5458         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5459       break;
5460     case VariantFalcon:
5461       pieces = FalconArray;
5462       gameInfo.boardWidth = 10;
5463       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5464       break;
5465     case VariantXiangqi:
5466       pieces = XiangqiArray;
5467       gameInfo.boardWidth  = 9;
5468       gameInfo.boardHeight = 10;
5469       nrCastlingRights = 0;
5470       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5471       break;
5472     case VariantShogi:
5473       pieces = ShogiArray;
5474       gameInfo.boardWidth  = 9;
5475       gameInfo.boardHeight = 9;
5476       gameInfo.holdingsSize = 7;
5477       nrCastlingRights = 0;
5478       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5479       break;
5480     case VariantCourier:
5481       pieces = CourierArray;
5482       gameInfo.boardWidth  = 12;
5483       nrCastlingRights = 0;
5484       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5485       break;
5486     case VariantKnightmate:
5487       pieces = KnightmateArray;
5488       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5489       break;
5490     case VariantSpartan:
5491       pieces = SpartanArray;
5492       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5493       break;
5494     case VariantFairy:
5495       pieces = fairyArray;
5496       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5497       break;
5498     case VariantGreat:
5499       pieces = GreatArray;
5500       gameInfo.boardWidth = 10;
5501       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5502       gameInfo.holdingsSize = 8;
5503       break;
5504     case VariantSuper:
5505       pieces = FIDEArray;
5506       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5507       gameInfo.holdingsSize = 8;
5508       startedFromSetupPosition = TRUE;
5509       break;
5510     case VariantCrazyhouse:
5511     case VariantBughouse:
5512       pieces = FIDEArray;
5513       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5514       gameInfo.holdingsSize = 5;
5515       break;
5516     case VariantWildCastle:
5517       pieces = FIDEArray;
5518       /* !!?shuffle with kings guaranteed to be on d or e file */
5519       shuffleOpenings = 1;
5520       break;
5521     case VariantNoCastle:
5522       pieces = FIDEArray;
5523       nrCastlingRights = 0;
5524       /* !!?unconstrained back-rank shuffle */
5525       shuffleOpenings = 1;
5526       break;
5527     }
5528
5529     overrule = 0;
5530     if(appData.NrFiles >= 0) {
5531         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5532         gameInfo.boardWidth = appData.NrFiles;
5533     }
5534     if(appData.NrRanks >= 0) {
5535         gameInfo.boardHeight = appData.NrRanks;
5536     }
5537     if(appData.holdingsSize >= 0) {
5538         i = appData.holdingsSize;
5539         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5540         gameInfo.holdingsSize = i;
5541     }
5542     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5543     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5544         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5545
5546     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5547     if(pawnRow < 1) pawnRow = 1;
5548     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5549
5550     /* User pieceToChar list overrules defaults */
5551     if(appData.pieceToCharTable != NULL)
5552         SetCharTable(pieceToChar, appData.pieceToCharTable);
5553
5554     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5555
5556         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5557             s = (ChessSquare) 0; /* account holding counts in guard band */
5558         for( i=0; i<BOARD_HEIGHT; i++ )
5559             initialPosition[i][j] = s;
5560
5561         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5562         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5563         initialPosition[pawnRow][j] = WhitePawn;
5564         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5565         if(gameInfo.variant == VariantXiangqi) {
5566             if(j&1) {
5567                 initialPosition[pawnRow][j] =
5568                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5569                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5570                    initialPosition[2][j] = WhiteCannon;
5571                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5572                 }
5573             }
5574         }
5575         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5576     }
5577     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5578
5579             j=BOARD_LEFT+1;
5580             initialPosition[1][j] = WhiteBishop;
5581             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5582             j=BOARD_RGHT-2;
5583             initialPosition[1][j] = WhiteRook;
5584             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5585     }
5586
5587     if( nrCastlingRights == -1) {
5588         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5589         /*       This sets default castling rights from none to normal corners   */
5590         /* Variants with other castling rights must set them themselves above    */
5591         nrCastlingRights = 6;
5592
5593         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5594         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5595         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5596         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5597         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5598         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5599      }
5600
5601      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5602      if(gameInfo.variant == VariantGreat) { // promotion commoners
5603         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5604         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5605         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5606         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5607      }
5608      if( gameInfo.variant == VariantSChess ) {
5609       initialPosition[1][0] = BlackMarshall;
5610       initialPosition[2][0] = BlackAngel;
5611       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5612       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5613       initialPosition[1][1] = initialPosition[2][1] = 
5614       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5615      }
5616   if (appData.debugMode) {
5617     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5618   }
5619     if(shuffleOpenings) {
5620         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5621         startedFromSetupPosition = TRUE;
5622     }
5623     if(startedFromPositionFile) {
5624       /* [HGM] loadPos: use PositionFile for every new game */
5625       CopyBoard(initialPosition, filePosition);
5626       for(i=0; i<nrCastlingRights; i++)
5627           initialRights[i] = filePosition[CASTLING][i];
5628       startedFromSetupPosition = TRUE;
5629     }
5630
5631     CopyBoard(boards[0], initialPosition);
5632
5633     if(oldx != gameInfo.boardWidth ||
5634        oldy != gameInfo.boardHeight ||
5635        oldv != gameInfo.variant ||
5636        oldh != gameInfo.holdingsWidth
5637                                          )
5638             InitDrawingSizes(-2 ,0);
5639
5640     oldv = gameInfo.variant;
5641     if (redraw)
5642       DrawPosition(TRUE, boards[currentMove]);
5643 }
5644
5645 void
5646 SendBoard(cps, moveNum)
5647      ChessProgramState *cps;
5648      int moveNum;
5649 {
5650     char message[MSG_SIZ];
5651
5652     if (cps->useSetboard) {
5653       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5654       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5655       SendToProgram(message, cps);
5656       free(fen);
5657
5658     } else {
5659       ChessSquare *bp;
5660       int i, j;
5661       /* Kludge to set black to move, avoiding the troublesome and now
5662        * deprecated "black" command.
5663        */
5664       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5665         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5666
5667       SendToProgram("edit\n", cps);
5668       SendToProgram("#\n", cps);
5669       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5670         bp = &boards[moveNum][i][BOARD_LEFT];
5671         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5672           if ((int) *bp < (int) BlackPawn) {
5673             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5674                     AAA + j, ONE + i);
5675             if(message[0] == '+' || message[0] == '~') {
5676               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5677                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5678                         AAA + j, ONE + i);
5679             }
5680             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5681                 message[1] = BOARD_RGHT   - 1 - j + '1';
5682                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5683             }
5684             SendToProgram(message, cps);
5685           }
5686         }
5687       }
5688
5689       SendToProgram("c\n", cps);
5690       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5691         bp = &boards[moveNum][i][BOARD_LEFT];
5692         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5693           if (((int) *bp != (int) EmptySquare)
5694               && ((int) *bp >= (int) BlackPawn)) {
5695             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5696                     AAA + j, ONE + i);
5697             if(message[0] == '+' || message[0] == '~') {
5698               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5699                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5700                         AAA + j, ONE + i);
5701             }
5702             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5703                 message[1] = BOARD_RGHT   - 1 - j + '1';
5704                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5705             }
5706             SendToProgram(message, cps);
5707           }
5708         }
5709       }
5710
5711       SendToProgram(".\n", cps);
5712     }
5713     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5714 }
5715
5716 ChessSquare
5717 DefaultPromoChoice(int white)
5718 {
5719     ChessSquare result;
5720     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5721         result = WhiteFerz; // no choice
5722     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5723         result= WhiteKing; // in Suicide Q is the last thing we want
5724     else if(gameInfo.variant == VariantSpartan)
5725         result = white ? WhiteQueen : WhiteAngel;
5726     else result = WhiteQueen;
5727     if(!white) result = WHITE_TO_BLACK result;
5728     return result;
5729 }
5730
5731 static int autoQueen; // [HGM] oneclick
5732
5733 int
5734 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5735 {
5736     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5737     /* [HGM] add Shogi promotions */
5738     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5739     ChessSquare piece;
5740     ChessMove moveType;
5741     Boolean premove;
5742
5743     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5744     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5745
5746     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5747       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5748         return FALSE;
5749
5750     piece = boards[currentMove][fromY][fromX];
5751     if(gameInfo.variant == VariantShogi) {
5752         promotionZoneSize = BOARD_HEIGHT/3;
5753         highestPromotingPiece = (int)WhiteFerz;
5754     } else if(gameInfo.variant == VariantMakruk) {
5755         promotionZoneSize = 3;
5756     }
5757
5758     // Treat Lance as Pawn when it is not representing Amazon
5759     if(gameInfo.variant != VariantSuper) {
5760         if(piece == WhiteLance) piece = WhitePawn; else
5761         if(piece == BlackLance) piece = BlackPawn;
5762     }
5763
5764     // next weed out all moves that do not touch the promotion zone at all
5765     if((int)piece >= BlackPawn) {
5766         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5767              return FALSE;
5768         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5769     } else {
5770         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5771            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5772     }
5773
5774     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5775
5776     // weed out mandatory Shogi promotions
5777     if(gameInfo.variant == VariantShogi) {
5778         if(piece >= BlackPawn) {
5779             if(toY == 0 && piece == BlackPawn ||
5780                toY == 0 && piece == BlackQueen ||
5781                toY <= 1 && piece == BlackKnight) {
5782                 *promoChoice = '+';
5783                 return FALSE;
5784             }
5785         } else {
5786             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5787                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5788                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5789                 *promoChoice = '+';
5790                 return FALSE;
5791             }
5792         }
5793     }
5794
5795     // weed out obviously illegal Pawn moves
5796     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5797         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5798         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5799         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5800         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5801         // note we are not allowed to test for valid (non-)capture, due to premove
5802     }
5803
5804     // we either have a choice what to promote to, or (in Shogi) whether to promote
5805     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5806         *promoChoice = PieceToChar(BlackFerz);  // no choice
5807         return FALSE;
5808     }
5809     // no sense asking what we must promote to if it is going to explode...
5810     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5811         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5812         return FALSE;
5813     }
5814     // give caller the default choice even if we will not make it
5815     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5816     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5817     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5818                            && gameInfo.variant != VariantShogi
5819                            && gameInfo.variant != VariantSuper) return FALSE;
5820     if(autoQueen) return FALSE; // predetermined
5821
5822     // suppress promotion popup on illegal moves that are not premoves
5823     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5824               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5825     if(appData.testLegality && !premove) {
5826         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5827                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5828         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5829             return FALSE;
5830     }
5831
5832     return TRUE;
5833 }
5834
5835 int
5836 InPalace(row, column)
5837      int row, column;
5838 {   /* [HGM] for Xiangqi */
5839     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5840          column < (BOARD_WIDTH + 4)/2 &&
5841          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5842     return FALSE;
5843 }
5844
5845 int
5846 PieceForSquare (x, y)
5847      int x;
5848      int y;
5849 {
5850   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5851      return -1;
5852   else
5853      return boards[currentMove][y][x];
5854 }
5855
5856 int
5857 OKToStartUserMove(x, y)
5858      int x, y;
5859 {
5860     ChessSquare from_piece;
5861     int white_piece;
5862
5863     if (matchMode) return FALSE;
5864     if (gameMode == EditPosition) return TRUE;
5865
5866     if (x >= 0 && y >= 0)
5867       from_piece = boards[currentMove][y][x];
5868     else
5869       from_piece = EmptySquare;
5870
5871     if (from_piece == EmptySquare) return FALSE;
5872
5873     white_piece = (int)from_piece >= (int)WhitePawn &&
5874       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5875
5876     switch (gameMode) {
5877       case PlayFromGameFile:
5878       case AnalyzeFile:
5879       case TwoMachinesPlay:
5880       case EndOfGame:
5881         return FALSE;
5882
5883       case IcsObserving:
5884       case IcsIdle:
5885         return FALSE;
5886
5887       case MachinePlaysWhite:
5888       case IcsPlayingBlack:
5889         if (appData.zippyPlay) return FALSE;
5890         if (white_piece) {
5891             DisplayMoveError(_("You are playing Black"));
5892             return FALSE;
5893         }
5894         break;
5895
5896       case MachinePlaysBlack:
5897       case IcsPlayingWhite:
5898         if (appData.zippyPlay) return FALSE;
5899         if (!white_piece) {
5900             DisplayMoveError(_("You are playing White"));
5901             return FALSE;
5902         }
5903         break;
5904
5905       case EditGame:
5906         if (!white_piece && WhiteOnMove(currentMove)) {
5907             DisplayMoveError(_("It is White's turn"));
5908             return FALSE;
5909         }
5910         if (white_piece && !WhiteOnMove(currentMove)) {
5911             DisplayMoveError(_("It is Black's turn"));
5912             return FALSE;
5913         }
5914         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5915             /* Editing correspondence game history */
5916             /* Could disallow this or prompt for confirmation */
5917             cmailOldMove = -1;
5918         }
5919         break;
5920
5921       case BeginningOfGame:
5922         if (appData.icsActive) return FALSE;
5923         if (!appData.noChessProgram) {
5924             if (!white_piece) {
5925                 DisplayMoveError(_("You are playing White"));
5926                 return FALSE;
5927             }
5928         }
5929         break;
5930
5931       case Training:
5932         if (!white_piece && WhiteOnMove(currentMove)) {
5933             DisplayMoveError(_("It is White's turn"));
5934             return FALSE;
5935         }
5936         if (white_piece && !WhiteOnMove(currentMove)) {
5937             DisplayMoveError(_("It is Black's turn"));
5938             return FALSE;
5939         }
5940         break;
5941
5942       default:
5943       case IcsExamining:
5944         break;
5945     }
5946     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5947         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5948         && gameMode != AnalyzeFile && gameMode != Training) {
5949         DisplayMoveError(_("Displayed position is not current"));
5950         return FALSE;
5951     }
5952     return TRUE;
5953 }
5954
5955 Boolean
5956 OnlyMove(int *x, int *y, Boolean captures) {
5957     DisambiguateClosure cl;
5958     if (appData.zippyPlay) return FALSE;
5959     switch(gameMode) {
5960       case MachinePlaysBlack:
5961       case IcsPlayingWhite:
5962       case BeginningOfGame:
5963         if(!WhiteOnMove(currentMove)) return FALSE;
5964         break;
5965       case MachinePlaysWhite:
5966       case IcsPlayingBlack:
5967         if(WhiteOnMove(currentMove)) return FALSE;
5968         break;
5969       case EditGame:
5970         break;
5971       default:
5972         return FALSE;
5973     }
5974     cl.pieceIn = EmptySquare;
5975     cl.rfIn = *y;
5976     cl.ffIn = *x;
5977     cl.rtIn = -1;
5978     cl.ftIn = -1;
5979     cl.promoCharIn = NULLCHAR;
5980     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5981     if( cl.kind == NormalMove ||
5982         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5983         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5984         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5985       fromX = cl.ff;
5986       fromY = cl.rf;
5987       *x = cl.ft;
5988       *y = cl.rt;
5989       return TRUE;
5990     }
5991     if(cl.kind != ImpossibleMove) return FALSE;
5992     cl.pieceIn = EmptySquare;
5993     cl.rfIn = -1;
5994     cl.ffIn = -1;
5995     cl.rtIn = *y;
5996     cl.ftIn = *x;
5997     cl.promoCharIn = NULLCHAR;
5998     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5999     if( cl.kind == NormalMove ||
6000         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6001         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6002         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6003       fromX = cl.ff;
6004       fromY = cl.rf;
6005       *x = cl.ft;
6006       *y = cl.rt;
6007       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6008       return TRUE;
6009     }
6010     return FALSE;
6011 }
6012
6013 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6014 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6015 int lastLoadGameUseList = FALSE;
6016 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6017 ChessMove lastLoadGameStart = EndOfFile;
6018
6019 void
6020 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6021      int fromX, fromY, toX, toY;
6022      int promoChar;
6023 {
6024     ChessMove moveType;
6025     ChessSquare pdown, pup;
6026
6027     /* Check if the user is playing in turn.  This is complicated because we
6028        let the user "pick up" a piece before it is his turn.  So the piece he
6029        tried to pick up may have been captured by the time he puts it down!
6030        Therefore we use the color the user is supposed to be playing in this
6031        test, not the color of the piece that is currently on the starting
6032        square---except in EditGame mode, where the user is playing both
6033        sides; fortunately there the capture race can't happen.  (It can
6034        now happen in IcsExamining mode, but that's just too bad.  The user
6035        will get a somewhat confusing message in that case.)
6036        */
6037
6038     switch (gameMode) {
6039       case PlayFromGameFile:
6040       case AnalyzeFile:
6041       case TwoMachinesPlay:
6042       case EndOfGame:
6043       case IcsObserving:
6044       case IcsIdle:
6045         /* We switched into a game mode where moves are not accepted,
6046            perhaps while the mouse button was down. */
6047         return;
6048
6049       case MachinePlaysWhite:
6050         /* User is moving for Black */
6051         if (WhiteOnMove(currentMove)) {
6052             DisplayMoveError(_("It is White's turn"));
6053             return;
6054         }
6055         break;
6056
6057       case MachinePlaysBlack:
6058         /* User is moving for White */
6059         if (!WhiteOnMove(currentMove)) {
6060             DisplayMoveError(_("It is Black's turn"));
6061             return;
6062         }
6063         break;
6064
6065       case EditGame:
6066       case IcsExamining:
6067       case BeginningOfGame:
6068       case AnalyzeMode:
6069       case Training:
6070         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6071         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6072             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6073             /* User is moving for Black */
6074             if (WhiteOnMove(currentMove)) {
6075                 DisplayMoveError(_("It is White's turn"));
6076                 return;
6077             }
6078         } else {
6079             /* User is moving for White */
6080             if (!WhiteOnMove(currentMove)) {
6081                 DisplayMoveError(_("It is Black's turn"));
6082                 return;
6083             }
6084         }
6085         break;
6086
6087       case IcsPlayingBlack:
6088         /* User is moving for Black */
6089         if (WhiteOnMove(currentMove)) {
6090             if (!appData.premove) {
6091                 DisplayMoveError(_("It is White's turn"));
6092             } else if (toX >= 0 && toY >= 0) {
6093                 premoveToX = toX;
6094                 premoveToY = toY;
6095                 premoveFromX = fromX;
6096                 premoveFromY = fromY;
6097                 premovePromoChar = promoChar;
6098                 gotPremove = 1;
6099                 if (appData.debugMode)
6100                     fprintf(debugFP, "Got premove: fromX %d,"
6101                             "fromY %d, toX %d, toY %d\n",
6102                             fromX, fromY, toX, toY);
6103             }
6104             return;
6105         }
6106         break;
6107
6108       case IcsPlayingWhite:
6109         /* User is moving for White */
6110         if (!WhiteOnMove(currentMove)) {
6111             if (!appData.premove) {
6112                 DisplayMoveError(_("It is Black's turn"));
6113             } else if (toX >= 0 && toY >= 0) {
6114                 premoveToX = toX;
6115                 premoveToY = toY;
6116                 premoveFromX = fromX;
6117                 premoveFromY = fromY;
6118                 premovePromoChar = promoChar;
6119                 gotPremove = 1;
6120                 if (appData.debugMode)
6121                     fprintf(debugFP, "Got premove: fromX %d,"
6122                             "fromY %d, toX %d, toY %d\n",
6123                             fromX, fromY, toX, toY);
6124             }
6125             return;
6126         }
6127         break;
6128
6129       default:
6130         break;
6131
6132       case EditPosition:
6133         /* EditPosition, empty square, or different color piece;
6134            click-click move is possible */
6135         if (toX == -2 || toY == -2) {
6136             boards[0][fromY][fromX] = EmptySquare;
6137             DrawPosition(FALSE, boards[currentMove]);
6138             return;
6139         } else if (toX >= 0 && toY >= 0) {
6140             boards[0][toY][toX] = boards[0][fromY][fromX];
6141             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6142                 if(boards[0][fromY][0] != EmptySquare) {
6143                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6144                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6145                 }
6146             } else
6147             if(fromX == BOARD_RGHT+1) {
6148                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6149                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6150                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6151                 }
6152             } else
6153             boards[0][fromY][fromX] = EmptySquare;
6154             DrawPosition(FALSE, boards[currentMove]);
6155             return;
6156         }
6157         return;
6158     }
6159
6160     if(toX < 0 || toY < 0) return;
6161     pdown = boards[currentMove][fromY][fromX];
6162     pup = boards[currentMove][toY][toX];
6163
6164     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6165     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6166          if( pup != EmptySquare ) return;
6167          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6168            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6169                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6170            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6171            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6172            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6173            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6174          fromY = DROP_RANK;
6175     }
6176
6177     /* [HGM] always test for legality, to get promotion info */
6178     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6179                                          fromY, fromX, toY, toX, promoChar);
6180     /* [HGM] but possibly ignore an IllegalMove result */
6181     if (appData.testLegality) {
6182         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6183             DisplayMoveError(_("Illegal move"));
6184             return;
6185         }
6186     }
6187
6188     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6189 }
6190
6191 /* Common tail of UserMoveEvent and DropMenuEvent */
6192 int
6193 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6194      ChessMove moveType;
6195      int fromX, fromY, toX, toY;
6196      /*char*/int promoChar;
6197 {
6198     char *bookHit = 0;
6199
6200     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6201         // [HGM] superchess: suppress promotions to non-available piece
6202         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6203         if(WhiteOnMove(currentMove)) {
6204             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6205         } else {
6206             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6207         }
6208     }
6209
6210     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6211        move type in caller when we know the move is a legal promotion */
6212     if(moveType == NormalMove && promoChar)
6213         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6214
6215     /* [HGM] <popupFix> The following if has been moved here from
6216        UserMoveEvent(). Because it seemed to belong here (why not allow
6217        piece drops in training games?), and because it can only be
6218        performed after it is known to what we promote. */
6219     if (gameMode == Training) {
6220       /* compare the move played on the board to the next move in the
6221        * game. If they match, display the move and the opponent's response.
6222        * If they don't match, display an error message.
6223        */
6224       int saveAnimate;
6225       Board testBoard;
6226       CopyBoard(testBoard, boards[currentMove]);
6227       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6228
6229       if (CompareBoards(testBoard, boards[currentMove+1])) {
6230         ForwardInner(currentMove+1);
6231
6232         /* Autoplay the opponent's response.
6233          * if appData.animate was TRUE when Training mode was entered,
6234          * the response will be animated.
6235          */
6236         saveAnimate = appData.animate;
6237         appData.animate = animateTraining;
6238         ForwardInner(currentMove+1);
6239         appData.animate = saveAnimate;
6240
6241         /* check for the end of the game */
6242         if (currentMove >= forwardMostMove) {
6243           gameMode = PlayFromGameFile;
6244           ModeHighlight();
6245           SetTrainingModeOff();
6246           DisplayInformation(_("End of game"));
6247         }
6248       } else {
6249         DisplayError(_("Incorrect move"), 0);
6250       }
6251       return 1;
6252     }
6253
6254   /* Ok, now we know that the move is good, so we can kill
6255      the previous line in Analysis Mode */
6256   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6257                                 && currentMove < forwardMostMove) {
6258     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6259     else forwardMostMove = currentMove;
6260   }
6261
6262   /* If we need the chess program but it's dead, restart it */
6263   ResurrectChessProgram();
6264
6265   /* A user move restarts a paused game*/
6266   if (pausing)
6267     PauseEvent();
6268
6269   thinkOutput[0] = NULLCHAR;
6270
6271   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6272
6273   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6274     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275     return 1;
6276   }
6277
6278   if (gameMode == BeginningOfGame) {
6279     if (appData.noChessProgram) {
6280       gameMode = EditGame;
6281       SetGameInfo();
6282     } else {
6283       char buf[MSG_SIZ];
6284       gameMode = MachinePlaysBlack;
6285       StartClocks();
6286       SetGameInfo();
6287       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6288       DisplayTitle(buf);
6289       if (first.sendName) {
6290         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6291         SendToProgram(buf, &first);
6292       }
6293       StartClocks();
6294     }
6295     ModeHighlight();
6296   }
6297
6298   /* Relay move to ICS or chess engine */
6299   if (appData.icsActive) {
6300     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6301         gameMode == IcsExamining) {
6302       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6303         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6304         SendToICS("draw ");
6305         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6306       }
6307       // also send plain move, in case ICS does not understand atomic claims
6308       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6309       ics_user_moved = 1;
6310     }
6311   } else {
6312     if (first.sendTime && (gameMode == BeginningOfGame ||
6313                            gameMode == MachinePlaysWhite ||
6314                            gameMode == MachinePlaysBlack)) {
6315       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6316     }
6317     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6318          // [HGM] book: if program might be playing, let it use book
6319         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6320         first.maybeThinking = TRUE;
6321     } else SendMoveToProgram(forwardMostMove-1, &first);
6322     if (currentMove == cmailOldMove + 1) {
6323       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6324     }
6325   }
6326
6327   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328
6329   switch (gameMode) {
6330   case EditGame:
6331     if(appData.testLegality)
6332     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6333     case MT_NONE:
6334     case MT_CHECK:
6335       break;
6336     case MT_CHECKMATE:
6337     case MT_STAINMATE:
6338       if (WhiteOnMove(currentMove)) {
6339         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6340       } else {
6341         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6342       }
6343       break;
6344     case MT_STALEMATE:
6345       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6346       break;
6347     }
6348     break;
6349
6350   case MachinePlaysBlack:
6351   case MachinePlaysWhite:
6352     /* disable certain menu options while machine is thinking */
6353     SetMachineThinkingEnables();
6354     break;
6355
6356   default:
6357     break;
6358   }
6359
6360   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6361   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6362
6363   if(bookHit) { // [HGM] book: simulate book reply
6364         static char bookMove[MSG_SIZ]; // a bit generous?
6365
6366         programStats.nodes = programStats.depth = programStats.time =
6367         programStats.score = programStats.got_only_move = 0;
6368         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6369
6370         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6371         strcat(bookMove, bookHit);
6372         HandleMachineMove(bookMove, &first);
6373   }
6374   return 1;
6375 }
6376
6377 void
6378 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6379      Board board;
6380      int flags;
6381      ChessMove kind;
6382      int rf, ff, rt, ft;
6383      VOIDSTAR closure;
6384 {
6385     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6386     Markers *m = (Markers *) closure;
6387     if(rf == fromY && ff == fromX)
6388         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6389                          || kind == WhiteCapturesEnPassant
6390                          || kind == BlackCapturesEnPassant);
6391     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6392 }
6393
6394 void
6395 MarkTargetSquares(int clear)
6396 {
6397   int x, y;
6398   if(!appData.markers || !appData.highlightDragging ||
6399      !appData.testLegality || gameMode == EditPosition) return;
6400   if(clear) {
6401     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6402   } else {
6403     int capt = 0;
6404     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6405     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6406       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6407       if(capt)
6408       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6409     }
6410   }
6411   DrawPosition(TRUE, NULL);
6412 }
6413
6414 int
6415 Explode(Board board, int fromX, int fromY, int toX, int toY)
6416 {
6417     if(gameInfo.variant == VariantAtomic &&
6418        (board[toY][toX] != EmptySquare ||                     // capture?
6419         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6420                          board[fromY][fromX] == BlackPawn   )
6421       )) {
6422         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6423         return TRUE;
6424     }
6425     return FALSE;
6426 }
6427
6428 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6429
6430 int CanPromote(ChessSquare piece, int y)
6431 {
6432         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6433         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6434         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6435            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6436            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6437                                                   gameInfo.variant == VariantMakruk) return FALSE;
6438         return (piece == BlackPawn && y == 1 ||
6439                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6440                 piece == BlackLance && y == 1 ||
6441                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6442 }
6443
6444 void LeftClick(ClickType clickType, int xPix, int yPix)
6445 {
6446     int x, y;
6447     Boolean saveAnimate;
6448     static int second = 0, promotionChoice = 0, clearFlag = 0;
6449     char promoChoice = NULLCHAR;
6450     ChessSquare piece;
6451
6452     if(appData.seekGraph && appData.icsActive && loggedOn &&
6453         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6454         SeekGraphClick(clickType, xPix, yPix, 0);
6455         return;
6456     }
6457
6458     if (clickType == Press) ErrorPopDown();
6459     MarkTargetSquares(1);
6460
6461     x = EventToSquare(xPix, BOARD_WIDTH);
6462     y = EventToSquare(yPix, BOARD_HEIGHT);
6463     if (!flipView && y >= 0) {
6464         y = BOARD_HEIGHT - 1 - y;
6465     }
6466     if (flipView && x >= 0) {
6467         x = BOARD_WIDTH - 1 - x;
6468     }
6469
6470     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6471         defaultPromoChoice = promoSweep;
6472         promoSweep = EmptySquare;   // terminate sweep
6473         promoDefaultAltered = TRUE;
6474         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6475     }
6476
6477     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6478         if(clickType == Release) return; // ignore upclick of click-click destination
6479         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6480         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6481         if(gameInfo.holdingsWidth &&
6482                 (WhiteOnMove(currentMove)
6483                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6484                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6485             // click in right holdings, for determining promotion piece
6486             ChessSquare p = boards[currentMove][y][x];
6487             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6488             if(p != EmptySquare) {
6489                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6490                 fromX = fromY = -1;
6491                 return;
6492             }
6493         }
6494         DrawPosition(FALSE, boards[currentMove]);
6495         return;
6496     }
6497
6498     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6499     if(clickType == Press
6500             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6501               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6502               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6503         return;
6504
6505     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6506         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6507
6508     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6509         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6510                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6511         defaultPromoChoice = DefaultPromoChoice(side);
6512     }
6513
6514     autoQueen = appData.alwaysPromoteToQueen;
6515
6516     if (fromX == -1) {
6517       int originalY = y;
6518       gatingPiece = EmptySquare;
6519       if (clickType != Press) {
6520         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6521             DragPieceEnd(xPix, yPix); dragging = 0;
6522             DrawPosition(FALSE, NULL);
6523         }
6524         return;
6525       }
6526       fromX = x; fromY = y;
6527       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6528          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6529          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6530             /* First square */
6531             if (OKToStartUserMove(fromX, fromY)) {
6532                 second = 0;
6533                 MarkTargetSquares(0);
6534                 DragPieceBegin(xPix, yPix); dragging = 1;
6535                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6536                     promoSweep = defaultPromoChoice;
6537                     selectFlag = 0; lastX = xPix; lastY = yPix;
6538                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6539                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6540                 }
6541                 if (appData.highlightDragging) {
6542                     SetHighlights(fromX, fromY, -1, -1);
6543                 }
6544             } else fromX = fromY = -1;
6545             return;
6546         }
6547     }
6548
6549     /* fromX != -1 */
6550     if (clickType == Press && gameMode != EditPosition) {
6551         ChessSquare fromP;
6552         ChessSquare toP;
6553         int frc;
6554
6555         // ignore off-board to clicks
6556         if(y < 0 || x < 0) return;
6557
6558         /* Check if clicking again on the same color piece */
6559         fromP = boards[currentMove][fromY][fromX];
6560         toP = boards[currentMove][y][x];
6561         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6562         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6563              WhitePawn <= toP && toP <= WhiteKing &&
6564              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6565              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6566             (BlackPawn <= fromP && fromP <= BlackKing &&
6567              BlackPawn <= toP && toP <= BlackKing &&
6568              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6569              !(fromP == BlackKing && toP == BlackRook && frc))) {
6570             /* Clicked again on same color piece -- changed his mind */
6571             second = (x == fromX && y == fromY);
6572             promoDefaultAltered = FALSE;
6573            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6574             if (appData.highlightDragging) {
6575                 SetHighlights(x, y, -1, -1);
6576             } else {
6577                 ClearHighlights();
6578             }
6579             if (OKToStartUserMove(x, y)) {
6580                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6581                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6582                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6583                  gatingPiece = boards[currentMove][fromY][fromX];
6584                 else gatingPiece = EmptySquare;
6585                 fromX = x;
6586                 fromY = y; dragging = 1;
6587                 MarkTargetSquares(0);
6588                 DragPieceBegin(xPix, yPix);
6589                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6590                     promoSweep = defaultPromoChoice;
6591                     selectFlag = 0; lastX = xPix; lastY = yPix;
6592                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6593                 }
6594             }
6595            }
6596            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6597            second = FALSE; 
6598         }
6599         // ignore clicks on holdings
6600         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6601     }
6602
6603     if (clickType == Release && x == fromX && y == fromY) {
6604         DragPieceEnd(xPix, yPix); dragging = 0;
6605         if(clearFlag) {
6606             // a deferred attempt to click-click move an empty square on top of a piece
6607             boards[currentMove][y][x] = EmptySquare;
6608             ClearHighlights();
6609             DrawPosition(FALSE, boards[currentMove]);
6610             fromX = fromY = -1; clearFlag = 0;
6611             return;
6612         }
6613         if (appData.animateDragging) {
6614             /* Undo animation damage if any */
6615             DrawPosition(FALSE, NULL);
6616         }
6617         if (second) {
6618             /* Second up/down in same square; just abort move */
6619             second = 0;
6620             fromX = fromY = -1;
6621             gatingPiece = EmptySquare;
6622             ClearHighlights();
6623             gotPremove = 0;
6624             ClearPremoveHighlights();
6625         } else {
6626             /* First upclick in same square; start click-click mode */
6627             SetHighlights(x, y, -1, -1);
6628         }
6629         return;
6630     }
6631
6632     clearFlag = 0;
6633
6634     /* we now have a different from- and (possibly off-board) to-square */
6635     /* Completed move */
6636     toX = x;
6637     toY = y;
6638     saveAnimate = appData.animate;
6639     if (clickType == Press) {
6640         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6641             // must be Edit Position mode with empty-square selected
6642             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6643             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6644             return;
6645         }
6646         /* Finish clickclick move */
6647         if (appData.animate || appData.highlightLastMove) {
6648             SetHighlights(fromX, fromY, toX, toY);
6649         } else {
6650             ClearHighlights();
6651         }
6652     } else {
6653         /* Finish drag move */
6654         if (appData.highlightLastMove) {
6655             SetHighlights(fromX, fromY, toX, toY);
6656         } else {
6657             ClearHighlights();
6658         }
6659         DragPieceEnd(xPix, yPix); dragging = 0;
6660         /* Don't animate move and drag both */
6661         appData.animate = FALSE;
6662     }
6663
6664     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6665     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6666         ChessSquare piece = boards[currentMove][fromY][fromX];
6667         if(gameMode == EditPosition && piece != EmptySquare &&
6668            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6669             int n;
6670
6671             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6672                 n = PieceToNumber(piece - (int)BlackPawn);
6673                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6674                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6675                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6676             } else
6677             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6678                 n = PieceToNumber(piece);
6679                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6680                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6681                 boards[currentMove][n][BOARD_WIDTH-2]++;
6682             }
6683             boards[currentMove][fromY][fromX] = EmptySquare;
6684         }
6685         ClearHighlights();
6686         fromX = fromY = -1;
6687         DrawPosition(TRUE, boards[currentMove]);
6688         return;
6689     }
6690
6691     // off-board moves should not be highlighted
6692     if(x < 0 || y < 0) ClearHighlights();
6693
6694     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6695
6696     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6697         SetHighlights(fromX, fromY, toX, toY);
6698         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6699             // [HGM] super: promotion to captured piece selected from holdings
6700             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6701             promotionChoice = TRUE;
6702             // kludge follows to temporarily execute move on display, without promoting yet
6703             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6704             boards[currentMove][toY][toX] = p;
6705             DrawPosition(FALSE, boards[currentMove]);
6706             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6707             boards[currentMove][toY][toX] = q;
6708             DisplayMessage("Click in holdings to choose piece", "");
6709             return;
6710         }
6711         PromotionPopUp();
6712     } else {
6713         int oldMove = currentMove;
6714         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6715         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6716         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6717         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6718            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6719             DrawPosition(TRUE, boards[currentMove]);
6720         fromX = fromY = -1;
6721     }
6722     appData.animate = saveAnimate;
6723     if (appData.animate || appData.animateDragging) {
6724         /* Undo animation damage if needed */
6725         DrawPosition(FALSE, NULL);
6726     }
6727 }
6728
6729 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6730 {   // front-end-free part taken out of PieceMenuPopup
6731     int whichMenu; int xSqr, ySqr;
6732
6733     if(seekGraphUp) { // [HGM] seekgraph
6734         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6735         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6736         return -2;
6737     }
6738
6739     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6740          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6741         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6742         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6743         if(action == Press)   {
6744             originalFlip = flipView;
6745             flipView = !flipView; // temporarily flip board to see game from partners perspective
6746             DrawPosition(TRUE, partnerBoard);
6747             DisplayMessage(partnerStatus, "");
6748             partnerUp = TRUE;
6749         } else if(action == Release) {
6750             flipView = originalFlip;
6751             DrawPosition(TRUE, boards[currentMove]);
6752             partnerUp = FALSE;
6753         }
6754         return -2;
6755     }
6756
6757     xSqr = EventToSquare(x, BOARD_WIDTH);
6758     ySqr = EventToSquare(y, BOARD_HEIGHT);
6759     if (action == Release) {
6760         if(pieceSweep != EmptySquare) {
6761             EditPositionMenuEvent(pieceSweep, toX, toY);
6762             pieceSweep = EmptySquare;
6763         } else UnLoadPV(); // [HGM] pv
6764     }
6765     if (action != Press) return -2; // return code to be ignored
6766     switch (gameMode) {
6767       case IcsExamining:
6768         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6769       case EditPosition:
6770         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6771         if (xSqr < 0 || ySqr < 0) return -1;
6772         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6773         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6774         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6775         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6776         NextPiece(0);
6777         return -2;\r
6778       case IcsObserving:
6779         if(!appData.icsEngineAnalyze) return -1;
6780       case IcsPlayingWhite:
6781       case IcsPlayingBlack:
6782         if(!appData.zippyPlay) goto noZip;
6783       case AnalyzeMode:
6784       case AnalyzeFile:
6785       case MachinePlaysWhite:
6786       case MachinePlaysBlack:
6787       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6788         if (!appData.dropMenu) {
6789           LoadPV(x, y);
6790           return 2; // flag front-end to grab mouse events
6791         }
6792         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6793            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6794       case EditGame:
6795       noZip:
6796         if (xSqr < 0 || ySqr < 0) return -1;
6797         if (!appData.dropMenu || appData.testLegality &&
6798             gameInfo.variant != VariantBughouse &&
6799             gameInfo.variant != VariantCrazyhouse) return -1;
6800         whichMenu = 1; // drop menu
6801         break;
6802       default:
6803         return -1;
6804     }
6805
6806     if (((*fromX = xSqr) < 0) ||
6807         ((*fromY = ySqr) < 0)) {
6808         *fromX = *fromY = -1;
6809         return -1;
6810     }
6811     if (flipView)
6812       *fromX = BOARD_WIDTH - 1 - *fromX;
6813     else
6814       *fromY = BOARD_HEIGHT - 1 - *fromY;
6815
6816     return whichMenu;
6817 }
6818
6819 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6820 {
6821 //    char * hint = lastHint;
6822     FrontEndProgramStats stats;
6823
6824     stats.which = cps == &first ? 0 : 1;
6825     stats.depth = cpstats->depth;
6826     stats.nodes = cpstats->nodes;
6827     stats.score = cpstats->score;
6828     stats.time = cpstats->time;
6829     stats.pv = cpstats->movelist;
6830     stats.hint = lastHint;
6831     stats.an_move_index = 0;
6832     stats.an_move_count = 0;
6833
6834     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6835         stats.hint = cpstats->move_name;
6836         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6837         stats.an_move_count = cpstats->nr_moves;
6838     }
6839
6840     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
6841
6842     SetProgramStats( &stats );
6843 }
6844
6845 void
6846 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6847 {       // count all piece types
6848         int p, f, r;
6849         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6850         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6851         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6852                 p = board[r][f];
6853                 pCnt[p]++;
6854                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6855                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6856                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6857                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6858                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6859                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6860         }
6861 }
6862
6863 int
6864 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6865 {
6866         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6867         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6868
6869         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6870         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6871         if(myPawns == 2 && nMine == 3) // KPP
6872             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6873         if(myPawns == 1 && nMine == 2) // KP
6874             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6875         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6876             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6877         if(myPawns) return FALSE;
6878         if(pCnt[WhiteRook+side])
6879             return pCnt[BlackRook-side] ||
6880                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6881                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6882                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6883         if(pCnt[WhiteCannon+side]) {
6884             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6885             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6886         }
6887         if(pCnt[WhiteKnight+side])
6888             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6889         return FALSE;
6890 }
6891
6892 int
6893 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6894 {
6895         VariantClass v = gameInfo.variant;
6896
6897         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6898         if(v == VariantShatranj) return TRUE; // always winnable through baring
6899         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6900         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6901
6902         if(v == VariantXiangqi) {
6903                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6904
6905                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6906                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6907                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6908                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6909                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6910                 if(stale) // we have at least one last-rank P plus perhaps C
6911                     return majors // KPKX
6912                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6913                 else // KCA*E*
6914                     return pCnt[WhiteFerz+side] // KCAK
6915                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6916                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6917                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6918
6919         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6920                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6921
6922                 if(nMine == 1) return FALSE; // bare King
6923                 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
6924                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6925                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6926                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6927                 if(pCnt[WhiteKnight+side])
6928                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6929                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6930                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6931                 if(nBishops)
6932                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6933                 if(pCnt[WhiteAlfil+side])
6934                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6935                 if(pCnt[WhiteWazir+side])
6936                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6937         }
6938
6939         return TRUE;
6940 }
6941
6942 int
6943 Adjudicate(ChessProgramState *cps)
6944 {       // [HGM] some adjudications useful with buggy engines
6945         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6946         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6947         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6948         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6949         int k, count = 0; static int bare = 1;
6950         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6951         Boolean canAdjudicate = !appData.icsActive;
6952
6953         // most tests only when we understand the game, i.e. legality-checking on
6954             if( appData.testLegality )
6955             {   /* [HGM] Some more adjudications for obstinate engines */
6956                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6957                 static int moveCount = 6;
6958                 ChessMove result;
6959                 char *reason = NULL;
6960
6961                 /* Count what is on board. */
6962                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6963
6964                 /* Some material-based adjudications that have to be made before stalemate test */
6965                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6966                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6967                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6968                      if(canAdjudicate && appData.checkMates) {
6969                          if(engineOpponent)
6970                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6971                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6972                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6973                          return 1;
6974                      }
6975                 }
6976
6977                 /* Bare King in Shatranj (loses) or Losers (wins) */
6978                 if( nrW == 1 || nrB == 1) {
6979                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6980                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6981                      if(canAdjudicate && appData.checkMates) {
6982                          if(engineOpponent)
6983                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6984                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6985                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6986                          return 1;
6987                      }
6988                   } else
6989                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6990                   {    /* bare King */
6991                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6992                         if(canAdjudicate && appData.checkMates) {
6993                             /* but only adjudicate if adjudication enabled */
6994                             if(engineOpponent)
6995                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6996                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6997                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6998                             return 1;
6999                         }
7000                   }
7001                 } else bare = 1;
7002
7003
7004             // don't wait for engine to announce game end if we can judge ourselves
7005             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7006               case MT_CHECK:
7007                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7008                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7009                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7010                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7011                             checkCnt++;
7012                         if(checkCnt >= 2) {
7013                             reason = "Xboard adjudication: 3rd check";
7014                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7015                             break;
7016                         }
7017                     }
7018                 }
7019               case MT_NONE:
7020               default:
7021                 break;
7022               case MT_STALEMATE:
7023               case MT_STAINMATE:
7024                 reason = "Xboard adjudication: Stalemate";
7025                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7026                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7027                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7028                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7029                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7030                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7031                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7032                                                                         EP_CHECKMATE : EP_WINS);
7033                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7034                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7035                 }
7036                 break;
7037               case MT_CHECKMATE:
7038                 reason = "Xboard adjudication: Checkmate";
7039                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7040                 break;
7041             }
7042
7043                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7044                     case EP_STALEMATE:
7045                         result = GameIsDrawn; break;
7046                     case EP_CHECKMATE:
7047                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7048                     case EP_WINS:
7049                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7050                     default:
7051                         result = EndOfFile;
7052                 }
7053                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7054                     if(engineOpponent)
7055                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7056                     GameEnds( result, reason, GE_XBOARD );
7057                     return 1;
7058                 }
7059
7060                 /* Next absolutely insufficient mating material. */
7061                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7062                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7063                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7064
7065                      /* always flag draws, for judging claims */
7066                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7067
7068                      if(canAdjudicate && appData.materialDraws) {
7069                          /* but only adjudicate them if adjudication enabled */
7070                          if(engineOpponent) {
7071                            SendToProgram("force\n", engineOpponent); // suppress reply
7072                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7073                          }
7074                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7075                          return 1;
7076                      }
7077                 }
7078
7079                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7080                 if(gameInfo.variant == VariantXiangqi ?
7081                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7082                  : nrW + nrB == 4 &&
7083                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7084                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7085                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7086                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7087                    ) ) {
7088                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7089                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7090                           if(engineOpponent) {
7091                             SendToProgram("force\n", engineOpponent); // suppress reply
7092                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7093                           }
7094                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7095                           return 1;
7096                      }
7097                 } else moveCount = 6;
7098             }
7099         if (appData.debugMode) { int i;
7100             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7101                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7102                     appData.drawRepeats);
7103             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7104               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7105
7106         }
7107
7108         // Repetition draws and 50-move rule can be applied independently of legality testing
7109
7110                 /* Check for rep-draws */
7111                 count = 0;
7112                 for(k = forwardMostMove-2;
7113                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7114                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7115                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7116                     k-=2)
7117                 {   int rights=0;
7118                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7119                         /* compare castling rights */
7120                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7121                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7122                                 rights++; /* King lost rights, while rook still had them */
7123                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7124                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7125                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7126                                    rights++; /* but at least one rook lost them */
7127                         }
7128                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7129                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7130                                 rights++;
7131                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7132                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7133                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7134                                    rights++;
7135                         }
7136                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7137                             && appData.drawRepeats > 1) {
7138                              /* adjudicate after user-specified nr of repeats */
7139                              int result = GameIsDrawn;
7140                              char *details = "XBoard adjudication: repetition draw";
7141                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7142                                 // [HGM] xiangqi: check for forbidden perpetuals
7143                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7144                                 for(m=forwardMostMove; m>k; m-=2) {
7145                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7146                                         ourPerpetual = 0; // the current mover did not always check
7147                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7148                                         hisPerpetual = 0; // the opponent did not always check
7149                                 }
7150                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7151                                                                         ourPerpetual, hisPerpetual);
7152                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7153                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7154                                     details = "Xboard adjudication: perpetual checking";
7155                                 } else
7156                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7157                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7158                                 } else
7159                                 // Now check for perpetual chases
7160                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7161                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7162                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7163                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7164                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7165                                         details = "Xboard adjudication: perpetual chasing";
7166                                     } else
7167                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7168                                         break; // Abort repetition-checking loop.
7169                                 }
7170                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7171                              }
7172                              if(engineOpponent) {
7173                                SendToProgram("force\n", engineOpponent); // suppress reply
7174                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7175                              }
7176                              GameEnds( result, details, GE_XBOARD );
7177                              return 1;
7178                         }
7179                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7180                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7181                     }
7182                 }
7183
7184                 /* Now we test for 50-move draws. Determine ply count */
7185                 count = forwardMostMove;
7186                 /* look for last irreversble move */
7187                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7188                     count--;
7189                 /* if we hit starting position, add initial plies */
7190                 if( count == backwardMostMove )
7191                     count -= initialRulePlies;
7192                 count = forwardMostMove - count;
7193                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7194                         // adjust reversible move counter for checks in Xiangqi
7195                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7196                         if(i < backwardMostMove) i = backwardMostMove;
7197                         while(i <= forwardMostMove) {
7198                                 lastCheck = inCheck; // check evasion does not count
7199                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7200                                 if(inCheck || lastCheck) count--; // check does not count
7201                                 i++;
7202                         }
7203                 }
7204                 if( count >= 100)
7205                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7206                          /* this is used to judge if draw claims are legal */
7207                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7208                          if(engineOpponent) {
7209                            SendToProgram("force\n", engineOpponent); // suppress reply
7210                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7211                          }
7212                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7213                          return 1;
7214                 }
7215
7216                 /* if draw offer is pending, treat it as a draw claim
7217                  * when draw condition present, to allow engines a way to
7218                  * claim draws before making their move to avoid a race
7219                  * condition occurring after their move
7220                  */
7221                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7222                          char *p = NULL;
7223                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7224                              p = "Draw claim: 50-move rule";
7225                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7226                              p = "Draw claim: 3-fold repetition";
7227                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7228                              p = "Draw claim: insufficient mating material";
7229                          if( p != NULL && canAdjudicate) {
7230                              if(engineOpponent) {
7231                                SendToProgram("force\n", engineOpponent); // suppress reply
7232                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7233                              }
7234                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7235                              return 1;
7236                          }
7237                 }
7238
7239                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7240                     if(engineOpponent) {
7241                       SendToProgram("force\n", engineOpponent); // suppress reply
7242                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7243                     }
7244                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7245                     return 1;
7246                 }
7247         return 0;
7248 }
7249
7250 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7251 {   // [HGM] book: this routine intercepts moves to simulate book replies
7252     char *bookHit = NULL;
7253
7254     //first determine if the incoming move brings opponent into his book
7255     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7256         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7257     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7258     if(bookHit != NULL && !cps->bookSuspend) {
7259         // make sure opponent is not going to reply after receiving move to book position
7260         SendToProgram("force\n", cps);
7261         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7262     }
7263     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7264     // now arrange restart after book miss
7265     if(bookHit) {
7266         // after a book hit we never send 'go', and the code after the call to this routine
7267         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7268         char buf[MSG_SIZ];
7269         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7270         SendToProgram(buf, cps);
7271         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7272     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7273         SendToProgram("go\n", cps);
7274         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7275     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7276         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7277             SendToProgram("go\n", cps);
7278         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7279     }
7280     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7281 }
7282
7283 char *savedMessage;
7284 ChessProgramState *savedState;
7285 void DeferredBookMove(void)
7286 {
7287         if(savedState->lastPing != savedState->lastPong)
7288                     ScheduleDelayedEvent(DeferredBookMove, 10);
7289         else
7290         HandleMachineMove(savedMessage, savedState);
7291 }
7292
7293 void
7294 HandleMachineMove(message, cps)
7295      char *message;
7296      ChessProgramState *cps;
7297 {
7298     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7299     char realname[MSG_SIZ];
7300     int fromX, fromY, toX, toY;
7301     ChessMove moveType;
7302     char promoChar;
7303     char *p;
7304     int machineWhite;
7305     char *bookHit;
7306
7307     cps->userError = 0;
7308
7309 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7310     /*
7311      * Kludge to ignore BEL characters
7312      */
7313     while (*message == '\007') message++;
7314
7315     /*
7316      * [HGM] engine debug message: ignore lines starting with '#' character
7317      */
7318     if(cps->debug && *message == '#') return;
7319
7320     /*
7321      * Look for book output
7322      */
7323     if (cps == &first && bookRequested) {
7324         if (message[0] == '\t' || message[0] == ' ') {
7325             /* Part of the book output is here; append it */
7326             strcat(bookOutput, message);
7327             strcat(bookOutput, "  \n");
7328             return;
7329         } else if (bookOutput[0] != NULLCHAR) {
7330             /* All of book output has arrived; display it */
7331             char *p = bookOutput;
7332             while (*p != NULLCHAR) {
7333                 if (*p == '\t') *p = ' ';
7334                 p++;
7335             }
7336             DisplayInformation(bookOutput);
7337             bookRequested = FALSE;
7338             /* Fall through to parse the current output */
7339         }
7340     }
7341
7342     /*
7343      * Look for machine move.
7344      */
7345     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7346         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7347     {
7348         /* This method is only useful on engines that support ping */
7349         if (cps->lastPing != cps->lastPong) {
7350           if (gameMode == BeginningOfGame) {
7351             /* Extra move from before last new; ignore */
7352             if (appData.debugMode) {
7353                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7354             }
7355           } else {
7356             if (appData.debugMode) {
7357                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7358                         cps->which, gameMode);
7359             }
7360
7361             SendToProgram("undo\n", cps);
7362           }
7363           return;
7364         }
7365
7366         switch (gameMode) {
7367           case BeginningOfGame:
7368             /* Extra move from before last reset; ignore */
7369             if (appData.debugMode) {
7370                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7371             }
7372             return;
7373
7374           case EndOfGame:
7375           case IcsIdle:
7376           default:
7377             /* Extra move after we tried to stop.  The mode test is
7378                not a reliable way of detecting this problem, but it's
7379                the best we can do on engines that don't support ping.
7380             */
7381             if (appData.debugMode) {
7382                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7383                         cps->which, gameMode);
7384             }
7385             SendToProgram("undo\n", cps);
7386             return;
7387
7388           case MachinePlaysWhite:
7389           case IcsPlayingWhite:
7390             machineWhite = TRUE;
7391             break;
7392
7393           case MachinePlaysBlack:
7394           case IcsPlayingBlack:
7395             machineWhite = FALSE;
7396             break;
7397
7398           case TwoMachinesPlay:
7399             machineWhite = (cps->twoMachinesColor[0] == 'w');
7400             break;
7401         }
7402         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7403             if (appData.debugMode) {
7404                 fprintf(debugFP,
7405                         "Ignoring move out of turn by %s, gameMode %d"
7406                         ", forwardMost %d\n",
7407                         cps->which, gameMode, forwardMostMove);
7408             }
7409             return;
7410         }
7411
7412     if (appData.debugMode) { int f = forwardMostMove;
7413         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7414                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7415                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7416     }
7417         if(cps->alphaRank) AlphaRank(machineMove, 4);
7418         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7419                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7420             /* Machine move could not be parsed; ignore it. */
7421           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7422                     machineMove, _(cps->which));
7423             DisplayError(buf1, 0);
7424             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7425                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7426             if (gameMode == TwoMachinesPlay) {
7427               GameEnds(machineWhite ? BlackWins : WhiteWins,
7428                        buf1, GE_XBOARD);
7429             }
7430             return;
7431         }
7432
7433         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7434         /* So we have to redo legality test with true e.p. status here,  */
7435         /* to make sure an illegal e.p. capture does not slip through,   */
7436         /* to cause a forfeit on a justified illegal-move complaint      */
7437         /* of the opponent.                                              */
7438         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7439            ChessMove moveType;
7440            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7441                              fromY, fromX, toY, toX, promoChar);
7442             if (appData.debugMode) {
7443                 int i;
7444                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7445                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7446                 fprintf(debugFP, "castling rights\n");
7447             }
7448             if(moveType == IllegalMove) {
7449               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7450                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7451                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7452                            buf1, GE_XBOARD);
7453                 return;
7454            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7455            /* [HGM] Kludge to handle engines that send FRC-style castling
7456               when they shouldn't (like TSCP-Gothic) */
7457            switch(moveType) {
7458              case WhiteASideCastleFR:
7459              case BlackASideCastleFR:
7460                toX+=2;
7461                currentMoveString[2]++;
7462                break;
7463              case WhiteHSideCastleFR:
7464              case BlackHSideCastleFR:
7465                toX--;
7466                currentMoveString[2]--;
7467                break;
7468              default: ; // nothing to do, but suppresses warning of pedantic compilers
7469            }
7470         }
7471         hintRequested = FALSE;
7472         lastHint[0] = NULLCHAR;
7473         bookRequested = FALSE;
7474         /* Program may be pondering now */
7475         cps->maybeThinking = TRUE;
7476         if (cps->sendTime == 2) cps->sendTime = 1;
7477         if (cps->offeredDraw) cps->offeredDraw--;
7478
7479         /* [AS] Save move info*/
7480         pvInfoList[ forwardMostMove ].score = programStats.score;
7481         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7482         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7483
7484         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7485
7486         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7487         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7488             int count = 0;
7489
7490             while( count < adjudicateLossPlies ) {
7491                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7492
7493                 if( count & 1 ) {
7494                     score = -score; /* Flip score for winning side */
7495                 }
7496
7497                 if( score > adjudicateLossThreshold ) {
7498                     break;
7499                 }
7500
7501                 count++;
7502             }
7503
7504             if( count >= adjudicateLossPlies ) {
7505                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7506
7507                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7508                     "Xboard adjudication",
7509                     GE_XBOARD );
7510
7511                 return;
7512             }
7513         }
7514
7515         if(Adjudicate(cps)) {
7516             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7517             return; // [HGM] adjudicate: for all automatic game ends
7518         }
7519
7520 #if ZIPPY
7521         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7522             first.initDone) {
7523           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7524                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7525                 SendToICS("draw ");
7526                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7527           }
7528           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7529           ics_user_moved = 1;
7530           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7531                 char buf[3*MSG_SIZ];
7532
7533                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7534                         programStats.score / 100.,
7535                         programStats.depth,
7536                         programStats.time / 100.,
7537                         (unsigned int)programStats.nodes,
7538                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7539                         programStats.movelist);
7540                 SendToICS(buf);
7541 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7542           }
7543         }
7544 #endif
7545
7546         /* [AS] Clear stats for next move */
7547         ClearProgramStats();
7548         thinkOutput[0] = NULLCHAR;
7549         hiddenThinkOutputState = 0;
7550
7551         bookHit = NULL;
7552         if (gameMode == TwoMachinesPlay) {
7553             /* [HGM] relaying draw offers moved to after reception of move */
7554             /* and interpreting offer as claim if it brings draw condition */
7555             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7556                 SendToProgram("draw\n", cps->other);
7557             }
7558             if (cps->other->sendTime) {
7559                 SendTimeRemaining(cps->other,
7560                                   cps->other->twoMachinesColor[0] == 'w');
7561             }
7562             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7563             if (firstMove && !bookHit) {
7564                 firstMove = FALSE;
7565                 if (cps->other->useColors) {
7566                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7567                 }
7568                 SendToProgram("go\n", cps->other);
7569             }
7570             cps->other->maybeThinking = TRUE;
7571         }
7572
7573         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7574
7575         if (!pausing && appData.ringBellAfterMoves) {
7576             RingBell();
7577         }
7578
7579         /*
7580          * Reenable menu items that were disabled while
7581          * machine was thinking
7582          */
7583         if (gameMode != TwoMachinesPlay)
7584             SetUserThinkingEnables();
7585
7586         // [HGM] book: after book hit opponent has received move and is now in force mode
7587         // force the book reply into it, and then fake that it outputted this move by jumping
7588         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7589         if(bookHit) {
7590                 static char bookMove[MSG_SIZ]; // a bit generous?
7591
7592                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7593                 strcat(bookMove, bookHit);
7594                 message = bookMove;
7595                 cps = cps->other;
7596                 programStats.nodes = programStats.depth = programStats.time =
7597                 programStats.score = programStats.got_only_move = 0;
7598                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7599
7600                 if(cps->lastPing != cps->lastPong) {
7601                     savedMessage = message; // args for deferred call
7602                     savedState = cps;
7603                     ScheduleDelayedEvent(DeferredBookMove, 10);
7604                     return;
7605                 }
7606                 goto FakeBookMove;
7607         }
7608
7609         return;
7610     }
7611
7612     /* Set special modes for chess engines.  Later something general
7613      *  could be added here; for now there is just one kludge feature,
7614      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7615      *  when "xboard" is given as an interactive command.
7616      */
7617     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7618         cps->useSigint = FALSE;
7619         cps->useSigterm = FALSE;
7620     }
7621     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7622       ParseFeatures(message+8, cps);
7623       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7624     }
7625
7626     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7627       int dummy, s=6; char buf[MSG_SIZ];
7628       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7629       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7630       ParseFEN(boards[0], &dummy, message+s);
7631       DrawPosition(TRUE, boards[0]);
7632       startedFromSetupPosition = TRUE;
7633       return;
7634     }
7635     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7636      * want this, I was asked to put it in, and obliged.
7637      */
7638     if (!strncmp(message, "setboard ", 9)) {
7639         Board initial_position;
7640
7641         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7642
7643         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7644             DisplayError(_("Bad FEN received from engine"), 0);
7645             return ;
7646         } else {
7647            Reset(TRUE, FALSE);
7648            CopyBoard(boards[0], initial_position);
7649            initialRulePlies = FENrulePlies;
7650            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7651            else gameMode = MachinePlaysBlack;
7652            DrawPosition(FALSE, boards[currentMove]);
7653         }
7654         return;
7655     }
7656
7657     /*
7658      * Look for communication commands
7659      */
7660     if (!strncmp(message, "telluser ", 9)) {
7661         if(message[9] == '\\' && message[10] == '\\')
7662             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7663         DisplayNote(message + 9);
7664         return;
7665     }
7666     if (!strncmp(message, "tellusererror ", 14)) {
7667         cps->userError = 1;
7668         if(message[14] == '\\' && message[15] == '\\')
7669             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7670         DisplayError(message + 14, 0);
7671         return;
7672     }
7673     if (!strncmp(message, "tellopponent ", 13)) {
7674       if (appData.icsActive) {
7675         if (loggedOn) {
7676           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7677           SendToICS(buf1);
7678         }
7679       } else {
7680         DisplayNote(message + 13);
7681       }
7682       return;
7683     }
7684     if (!strncmp(message, "tellothers ", 11)) {
7685       if (appData.icsActive) {
7686         if (loggedOn) {
7687           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7688           SendToICS(buf1);
7689         }
7690       }
7691       return;
7692     }
7693     if (!strncmp(message, "tellall ", 8)) {
7694       if (appData.icsActive) {
7695         if (loggedOn) {
7696           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7697           SendToICS(buf1);
7698         }
7699       } else {
7700         DisplayNote(message + 8);
7701       }
7702       return;
7703     }
7704     if (strncmp(message, "warning", 7) == 0) {
7705         /* Undocumented feature, use tellusererror in new code */
7706         DisplayError(message, 0);
7707         return;
7708     }
7709     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7710         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7711         strcat(realname, " query");
7712         AskQuestion(realname, buf2, buf1, cps->pr);
7713         return;
7714     }
7715     /* Commands from the engine directly to ICS.  We don't allow these to be
7716      *  sent until we are logged on. Crafty kibitzes have been known to
7717      *  interfere with the login process.
7718      */
7719     if (loggedOn) {
7720         if (!strncmp(message, "tellics ", 8)) {
7721             SendToICS(message + 8);
7722             SendToICS("\n");
7723             return;
7724         }
7725         if (!strncmp(message, "tellicsnoalias ", 15)) {
7726             SendToICS(ics_prefix);
7727             SendToICS(message + 15);
7728             SendToICS("\n");
7729             return;
7730         }
7731         /* The following are for backward compatibility only */
7732         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7733             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7734             SendToICS(ics_prefix);
7735             SendToICS(message);
7736             SendToICS("\n");
7737             return;
7738         }
7739     }
7740     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7741         return;
7742     }
7743     /*
7744      * If the move is illegal, cancel it and redraw the board.
7745      * Also deal with other error cases.  Matching is rather loose
7746      * here to accommodate engines written before the spec.
7747      */
7748     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7749         strncmp(message, "Error", 5) == 0) {
7750         if (StrStr(message, "name") ||
7751             StrStr(message, "rating") || StrStr(message, "?") ||
7752             StrStr(message, "result") || StrStr(message, "board") ||
7753             StrStr(message, "bk") || StrStr(message, "computer") ||
7754             StrStr(message, "variant") || StrStr(message, "hint") ||
7755             StrStr(message, "random") || StrStr(message, "depth") ||
7756             StrStr(message, "accepted")) {
7757             return;
7758         }
7759         if (StrStr(message, "protover")) {
7760           /* Program is responding to input, so it's apparently done
7761              initializing, and this error message indicates it is
7762              protocol version 1.  So we don't need to wait any longer
7763              for it to initialize and send feature commands. */
7764           FeatureDone(cps, 1);
7765           cps->protocolVersion = 1;
7766           return;
7767         }
7768         cps->maybeThinking = FALSE;
7769
7770         if (StrStr(message, "draw")) {
7771             /* Program doesn't have "draw" command */
7772             cps->sendDrawOffers = 0;
7773             return;
7774         }
7775         if (cps->sendTime != 1 &&
7776             (StrStr(message, "time") || StrStr(message, "otim"))) {
7777           /* Program apparently doesn't have "time" or "otim" command */
7778           cps->sendTime = 0;
7779           return;
7780         }
7781         if (StrStr(message, "analyze")) {
7782             cps->analysisSupport = FALSE;
7783             cps->analyzing = FALSE;
7784             Reset(FALSE, TRUE);
7785             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7786             DisplayError(buf2, 0);
7787             return;
7788         }
7789         if (StrStr(message, "(no matching move)st")) {
7790           /* Special kludge for GNU Chess 4 only */
7791           cps->stKludge = TRUE;
7792           SendTimeControl(cps, movesPerSession, timeControl,
7793                           timeIncrement, appData.searchDepth,
7794                           searchTime);
7795           return;
7796         }
7797         if (StrStr(message, "(no matching move)sd")) {
7798           /* Special kludge for GNU Chess 4 only */
7799           cps->sdKludge = TRUE;
7800           SendTimeControl(cps, movesPerSession, timeControl,
7801                           timeIncrement, appData.searchDepth,
7802                           searchTime);
7803           return;
7804         }
7805         if (!StrStr(message, "llegal")) {
7806             return;
7807         }
7808         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7809             gameMode == IcsIdle) return;
7810         if (forwardMostMove <= backwardMostMove) return;
7811         if (pausing) PauseEvent();
7812       if(appData.forceIllegal) {
7813             // [HGM] illegal: machine refused move; force position after move into it
7814           SendToProgram("force\n", cps);
7815           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7816                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7817                 // when black is to move, while there might be nothing on a2 or black
7818                 // might already have the move. So send the board as if white has the move.
7819                 // But first we must change the stm of the engine, as it refused the last move
7820                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7821                 if(WhiteOnMove(forwardMostMove)) {
7822                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7823                     SendBoard(cps, forwardMostMove); // kludgeless board
7824                 } else {
7825                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7826                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7827                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7828                 }
7829           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7830             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7831                  gameMode == TwoMachinesPlay)
7832               SendToProgram("go\n", cps);
7833             return;
7834       } else
7835         if (gameMode == PlayFromGameFile) {
7836             /* Stop reading this game file */
7837             gameMode = EditGame;
7838             ModeHighlight();
7839         }
7840         /* [HGM] illegal-move claim should forfeit game when Xboard */
7841         /* only passes fully legal moves                            */
7842         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7843             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7844                                 "False illegal-move claim", GE_XBOARD );
7845             return; // do not take back move we tested as valid
7846         }
7847         currentMove = forwardMostMove-1;
7848         DisplayMove(currentMove-1); /* before DisplayMoveError */
7849         SwitchClocks(forwardMostMove-1); // [HGM] race
7850         DisplayBothClocks();
7851         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7852                 parseList[currentMove], _(cps->which));
7853         DisplayMoveError(buf1);
7854         DrawPosition(FALSE, boards[currentMove]);
7855         return;
7856     }
7857     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7858         /* Program has a broken "time" command that
7859            outputs a string not ending in newline.
7860            Don't use it. */
7861         cps->sendTime = 0;
7862     }
7863
7864     /*
7865      * If chess program startup fails, exit with an error message.
7866      * Attempts to recover here are futile.
7867      */
7868     if ((StrStr(message, "unknown host") != NULL)
7869         || (StrStr(message, "No remote directory") != NULL)
7870         || (StrStr(message, "not found") != NULL)
7871         || (StrStr(message, "No such file") != NULL)
7872         || (StrStr(message, "can't alloc") != NULL)
7873         || (StrStr(message, "Permission denied") != NULL)) {
7874
7875         cps->maybeThinking = FALSE;
7876         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7877                 _(cps->which), cps->program, cps->host, message);
7878         RemoveInputSource(cps->isr);
7879         DisplayFatalError(buf1, 0, 1);
7880         return;
7881     }
7882
7883     /*
7884      * Look for hint output
7885      */
7886     if (sscanf(message, "Hint: %s", buf1) == 1) {
7887         if (cps == &first && hintRequested) {
7888             hintRequested = FALSE;
7889             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7890                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7891                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7892                                     PosFlags(forwardMostMove),
7893                                     fromY, fromX, toY, toX, promoChar, buf1);
7894                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7895                 DisplayInformation(buf2);
7896             } else {
7897                 /* Hint move could not be parsed!? */
7898               snprintf(buf2, sizeof(buf2),
7899                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7900                         buf1, _(cps->which));
7901                 DisplayError(buf2, 0);
7902             }
7903         } else {
7904           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7905         }
7906         return;
7907     }
7908
7909     /*
7910      * Ignore other messages if game is not in progress
7911      */
7912     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7913         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7914
7915     /*
7916      * look for win, lose, draw, or draw offer
7917      */
7918     if (strncmp(message, "1-0", 3) == 0) {
7919         char *p, *q, *r = "";
7920         p = strchr(message, '{');
7921         if (p) {
7922             q = strchr(p, '}');
7923             if (q) {
7924                 *q = NULLCHAR;
7925                 r = p + 1;
7926             }
7927         }
7928         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7929         return;
7930     } else if (strncmp(message, "0-1", 3) == 0) {
7931         char *p, *q, *r = "";
7932         p = strchr(message, '{');
7933         if (p) {
7934             q = strchr(p, '}');
7935             if (q) {
7936                 *q = NULLCHAR;
7937                 r = p + 1;
7938             }
7939         }
7940         /* Kludge for Arasan 4.1 bug */
7941         if (strcmp(r, "Black resigns") == 0) {
7942             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7943             return;
7944         }
7945         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7946         return;
7947     } else if (strncmp(message, "1/2", 3) == 0) {
7948         char *p, *q, *r = "";
7949         p = strchr(message, '{');
7950         if (p) {
7951             q = strchr(p, '}');
7952             if (q) {
7953                 *q = NULLCHAR;
7954                 r = p + 1;
7955             }
7956         }
7957
7958         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7959         return;
7960
7961     } else if (strncmp(message, "White resign", 12) == 0) {
7962         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7963         return;
7964     } else if (strncmp(message, "Black resign", 12) == 0) {
7965         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7966         return;
7967     } else if (strncmp(message, "White matches", 13) == 0 ||
7968                strncmp(message, "Black matches", 13) == 0   ) {
7969         /* [HGM] ignore GNUShogi noises */
7970         return;
7971     } else if (strncmp(message, "White", 5) == 0 &&
7972                message[5] != '(' &&
7973                StrStr(message, "Black") == NULL) {
7974         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7975         return;
7976     } else if (strncmp(message, "Black", 5) == 0 &&
7977                message[5] != '(') {
7978         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7979         return;
7980     } else if (strcmp(message, "resign") == 0 ||
7981                strcmp(message, "computer resigns") == 0) {
7982         switch (gameMode) {
7983           case MachinePlaysBlack:
7984           case IcsPlayingBlack:
7985             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7986             break;
7987           case MachinePlaysWhite:
7988           case IcsPlayingWhite:
7989             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7990             break;
7991           case TwoMachinesPlay:
7992             if (cps->twoMachinesColor[0] == 'w')
7993               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7994             else
7995               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7996             break;
7997           default:
7998             /* can't happen */
7999             break;
8000         }
8001         return;
8002     } else if (strncmp(message, "opponent mates", 14) == 0) {
8003         switch (gameMode) {
8004           case MachinePlaysBlack:
8005           case IcsPlayingBlack:
8006             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8007             break;
8008           case MachinePlaysWhite:
8009           case IcsPlayingWhite:
8010             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8011             break;
8012           case TwoMachinesPlay:
8013             if (cps->twoMachinesColor[0] == 'w')
8014               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8015             else
8016               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8017             break;
8018           default:
8019             /* can't happen */
8020             break;
8021         }
8022         return;
8023     } else if (strncmp(message, "computer mates", 14) == 0) {
8024         switch (gameMode) {
8025           case MachinePlaysBlack:
8026           case IcsPlayingBlack:
8027             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8028             break;
8029           case MachinePlaysWhite:
8030           case IcsPlayingWhite:
8031             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8032             break;
8033           case TwoMachinesPlay:
8034             if (cps->twoMachinesColor[0] == 'w')
8035               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8036             else
8037               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8038             break;
8039           default:
8040             /* can't happen */
8041             break;
8042         }
8043         return;
8044     } else if (strncmp(message, "checkmate", 9) == 0) {
8045         if (WhiteOnMove(forwardMostMove)) {
8046             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8047         } else {
8048             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8049         }
8050         return;
8051     } else if (strstr(message, "Draw") != NULL ||
8052                strstr(message, "game is a draw") != NULL) {
8053         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8054         return;
8055     } else if (strstr(message, "offer") != NULL &&
8056                strstr(message, "draw") != NULL) {
8057 #if ZIPPY
8058         if (appData.zippyPlay && first.initDone) {
8059             /* Relay offer to ICS */
8060             SendToICS(ics_prefix);
8061             SendToICS("draw\n");
8062         }
8063 #endif
8064         cps->offeredDraw = 2; /* valid until this engine moves twice */
8065         if (gameMode == TwoMachinesPlay) {
8066             if (cps->other->offeredDraw) {
8067                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8068             /* [HGM] in two-machine mode we delay relaying draw offer      */
8069             /* until after we also have move, to see if it is really claim */
8070             }
8071         } else if (gameMode == MachinePlaysWhite ||
8072                    gameMode == MachinePlaysBlack) {
8073           if (userOfferedDraw) {
8074             DisplayInformation(_("Machine accepts your draw offer"));
8075             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8076           } else {
8077             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8078           }
8079         }
8080     }
8081
8082
8083     /*
8084      * Look for thinking output
8085      */
8086     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8087           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8088                                 ) {
8089         int plylev, mvleft, mvtot, curscore, time;
8090         char mvname[MOVE_LEN];
8091         u64 nodes; // [DM]
8092         char plyext;
8093         int ignore = FALSE;
8094         int prefixHint = FALSE;
8095         mvname[0] = NULLCHAR;
8096
8097         switch (gameMode) {
8098           case MachinePlaysBlack:
8099           case IcsPlayingBlack:
8100             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8101             break;
8102           case MachinePlaysWhite:
8103           case IcsPlayingWhite:
8104             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8105             break;
8106           case AnalyzeMode:
8107           case AnalyzeFile:
8108             break;
8109           case IcsObserving: /* [DM] icsEngineAnalyze */
8110             if (!appData.icsEngineAnalyze) ignore = TRUE;
8111             break;
8112           case TwoMachinesPlay:
8113             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8114                 ignore = TRUE;
8115             }
8116             break;
8117           default:
8118             ignore = TRUE;
8119             break;
8120         }
8121
8122         if (!ignore) {
8123             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8124             buf1[0] = NULLCHAR;
8125             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8126                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8127
8128                 if (plyext != ' ' && plyext != '\t') {
8129                     time *= 100;
8130                 }
8131
8132                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8133                 if( cps->scoreIsAbsolute &&
8134                     ( gameMode == MachinePlaysBlack ||
8135                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8136                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8137                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8138                      !WhiteOnMove(currentMove)
8139                     ) )
8140                 {
8141                     curscore = -curscore;
8142                 }
8143
8144
8145                 tempStats.depth = plylev;
8146                 tempStats.nodes = nodes;
8147                 tempStats.time = time;
8148                 tempStats.score = curscore;
8149                 tempStats.got_only_move = 0;
8150
8151                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8152                         int ticklen;
8153
8154                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8155                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8156                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8157                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8158                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8159                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8160                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8161                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8162                 }
8163
8164                 /* Buffer overflow protection */
8165                 if (buf1[0] != NULLCHAR) {
8166                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8167                         && appData.debugMode) {
8168                         fprintf(debugFP,
8169                                 "PV is too long; using the first %u bytes.\n",
8170                                 (unsigned) sizeof(tempStats.movelist) - 1);
8171                     }
8172
8173                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8174                 } else {
8175                     sprintf(tempStats.movelist, " no PV\n");
8176                 }
8177
8178                 if (tempStats.seen_stat) {
8179                     tempStats.ok_to_send = 1;
8180                 }
8181
8182                 if (strchr(tempStats.movelist, '(') != NULL) {
8183                     tempStats.line_is_book = 1;
8184                     tempStats.nr_moves = 0;
8185                     tempStats.moves_left = 0;
8186                 } else {
8187                     tempStats.line_is_book = 0;
8188                 }
8189
8190                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8191                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8192
8193                 SendProgramStatsToFrontend( cps, &tempStats );
8194
8195                 /*
8196                     [AS] Protect the thinkOutput buffer from overflow... this
8197                     is only useful if buf1 hasn't overflowed first!
8198                 */
8199                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8200                          plylev,
8201                          (gameMode == TwoMachinesPlay ?
8202                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8203                          ((double) curscore) / 100.0,
8204                          prefixHint ? lastHint : "",
8205                          prefixHint ? " " : "" );
8206
8207                 if( buf1[0] != NULLCHAR ) {
8208                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8209
8210                     if( strlen(buf1) > max_len ) {
8211                         if( appData.debugMode) {
8212                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8213                         }
8214                         buf1[max_len+1] = '\0';
8215                     }
8216
8217                     strcat( thinkOutput, buf1 );
8218                 }
8219
8220                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8221                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8222                     DisplayMove(currentMove - 1);
8223                 }
8224                 return;
8225
8226             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8227                 /* crafty (9.25+) says "(only move) <move>"
8228                  * if there is only 1 legal move
8229                  */
8230                 sscanf(p, "(only move) %s", buf1);
8231                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8232                 sprintf(programStats.movelist, "%s (only move)", buf1);
8233                 programStats.depth = 1;
8234                 programStats.nr_moves = 1;
8235                 programStats.moves_left = 1;
8236                 programStats.nodes = 1;
8237                 programStats.time = 1;
8238                 programStats.got_only_move = 1;
8239
8240                 /* Not really, but we also use this member to
8241                    mean "line isn't going to change" (Crafty
8242                    isn't searching, so stats won't change) */
8243                 programStats.line_is_book = 1;
8244
8245                 SendProgramStatsToFrontend( cps, &programStats );
8246
8247                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8248                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8249                     DisplayMove(currentMove - 1);
8250                 }
8251                 return;
8252             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8253                               &time, &nodes, &plylev, &mvleft,
8254                               &mvtot, mvname) >= 5) {
8255                 /* The stat01: line is from Crafty (9.29+) in response
8256                    to the "." command */
8257                 programStats.seen_stat = 1;
8258                 cps->maybeThinking = TRUE;
8259
8260                 if (programStats.got_only_move || !appData.periodicUpdates)
8261                   return;
8262
8263                 programStats.depth = plylev;
8264                 programStats.time = time;
8265                 programStats.nodes = nodes;
8266                 programStats.moves_left = mvleft;
8267                 programStats.nr_moves = mvtot;
8268                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8269                 programStats.ok_to_send = 1;
8270                 programStats.movelist[0] = '\0';
8271
8272                 SendProgramStatsToFrontend( cps, &programStats );
8273
8274                 return;
8275
8276             } else if (strncmp(message,"++",2) == 0) {
8277                 /* Crafty 9.29+ outputs this */
8278                 programStats.got_fail = 2;
8279                 return;
8280
8281             } else if (strncmp(message,"--",2) == 0) {
8282                 /* Crafty 9.29+ outputs this */
8283                 programStats.got_fail = 1;
8284                 return;
8285
8286             } else if (thinkOutput[0] != NULLCHAR &&
8287                        strncmp(message, "    ", 4) == 0) {
8288                 unsigned message_len;
8289
8290                 p = message;
8291                 while (*p && *p == ' ') p++;
8292
8293                 message_len = strlen( p );
8294
8295                 /* [AS] Avoid buffer overflow */
8296                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8297                     strcat(thinkOutput, " ");
8298                     strcat(thinkOutput, p);
8299                 }
8300
8301                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8302                     strcat(programStats.movelist, " ");
8303                     strcat(programStats.movelist, p);
8304                 }
8305
8306                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8307                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8308                     DisplayMove(currentMove - 1);
8309                 }
8310                 return;
8311             }
8312         }
8313         else {
8314             buf1[0] = NULLCHAR;
8315
8316             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8317                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8318             {
8319                 ChessProgramStats cpstats;
8320
8321                 if (plyext != ' ' && plyext != '\t') {
8322                     time *= 100;
8323                 }
8324
8325                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8326                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8327                     curscore = -curscore;
8328                 }
8329
8330                 cpstats.depth = plylev;
8331                 cpstats.nodes = nodes;
8332                 cpstats.time = time;
8333                 cpstats.score = curscore;
8334                 cpstats.got_only_move = 0;
8335                 cpstats.movelist[0] = '\0';
8336
8337                 if (buf1[0] != NULLCHAR) {
8338                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8339                 }
8340
8341                 cpstats.ok_to_send = 0;
8342                 cpstats.line_is_book = 0;
8343                 cpstats.nr_moves = 0;
8344                 cpstats.moves_left = 0;
8345
8346                 SendProgramStatsToFrontend( cps, &cpstats );
8347             }
8348         }
8349     }
8350 }
8351
8352
8353 /* Parse a game score from the character string "game", and
8354    record it as the history of the current game.  The game
8355    score is NOT assumed to start from the standard position.
8356    The display is not updated in any way.
8357    */
8358 void
8359 ParseGameHistory(game)
8360      char *game;
8361 {
8362     ChessMove moveType;
8363     int fromX, fromY, toX, toY, boardIndex;
8364     char promoChar;
8365     char *p, *q;
8366     char buf[MSG_SIZ];
8367
8368     if (appData.debugMode)
8369       fprintf(debugFP, "Parsing game history: %s\n", game);
8370
8371     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8372     gameInfo.site = StrSave(appData.icsHost);
8373     gameInfo.date = PGNDate();
8374     gameInfo.round = StrSave("-");
8375
8376     /* Parse out names of players */
8377     while (*game == ' ') game++;
8378     p = buf;
8379     while (*game != ' ') *p++ = *game++;
8380     *p = NULLCHAR;
8381     gameInfo.white = StrSave(buf);
8382     while (*game == ' ') game++;
8383     p = buf;
8384     while (*game != ' ' && *game != '\n') *p++ = *game++;
8385     *p = NULLCHAR;
8386     gameInfo.black = StrSave(buf);
8387
8388     /* Parse moves */
8389     boardIndex = blackPlaysFirst ? 1 : 0;
8390     yynewstr(game);
8391     for (;;) {
8392         yyboardindex = boardIndex;
8393         moveType = (ChessMove) Myylex();
8394         switch (moveType) {
8395           case IllegalMove:             /* maybe suicide chess, etc. */
8396   if (appData.debugMode) {
8397     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8398     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8399     setbuf(debugFP, NULL);
8400   }
8401           case WhitePromotion:
8402           case BlackPromotion:
8403           case WhiteNonPromotion:
8404           case BlackNonPromotion:
8405           case NormalMove:
8406           case WhiteCapturesEnPassant:
8407           case BlackCapturesEnPassant:
8408           case WhiteKingSideCastle:
8409           case WhiteQueenSideCastle:
8410           case BlackKingSideCastle:
8411           case BlackQueenSideCastle:
8412           case WhiteKingSideCastleWild:
8413           case WhiteQueenSideCastleWild:
8414           case BlackKingSideCastleWild:
8415           case BlackQueenSideCastleWild:
8416           /* PUSH Fabien */
8417           case WhiteHSideCastleFR:
8418           case WhiteASideCastleFR:
8419           case BlackHSideCastleFR:
8420           case BlackASideCastleFR:
8421           /* POP Fabien */
8422             fromX = currentMoveString[0] - AAA;
8423             fromY = currentMoveString[1] - ONE;
8424             toX = currentMoveString[2] - AAA;
8425             toY = currentMoveString[3] - ONE;
8426             promoChar = currentMoveString[4];
8427             break;
8428           case WhiteDrop:
8429           case BlackDrop:
8430             fromX = moveType == WhiteDrop ?
8431               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8432             (int) CharToPiece(ToLower(currentMoveString[0]));
8433             fromY = DROP_RANK;
8434             toX = currentMoveString[2] - AAA;
8435             toY = currentMoveString[3] - ONE;
8436             promoChar = NULLCHAR;
8437             break;
8438           case AmbiguousMove:
8439             /* bug? */
8440             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8441   if (appData.debugMode) {
8442     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8443     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8444     setbuf(debugFP, NULL);
8445   }
8446             DisplayError(buf, 0);
8447             return;
8448           case ImpossibleMove:
8449             /* bug? */
8450             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8451   if (appData.debugMode) {
8452     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8453     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8454     setbuf(debugFP, NULL);
8455   }
8456             DisplayError(buf, 0);
8457             return;
8458           case EndOfFile:
8459             if (boardIndex < backwardMostMove) {
8460                 /* Oops, gap.  How did that happen? */
8461                 DisplayError(_("Gap in move list"), 0);
8462                 return;
8463             }
8464             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8465             if (boardIndex > forwardMostMove) {
8466                 forwardMostMove = boardIndex;
8467             }
8468             return;
8469           case ElapsedTime:
8470             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8471                 strcat(parseList[boardIndex-1], " ");
8472                 strcat(parseList[boardIndex-1], yy_text);
8473             }
8474             continue;
8475           case Comment:
8476           case PGNTag:
8477           case NAG:
8478           default:
8479             /* ignore */
8480             continue;
8481           case WhiteWins:
8482           case BlackWins:
8483           case GameIsDrawn:
8484           case GameUnfinished:
8485             if (gameMode == IcsExamining) {
8486                 if (boardIndex < backwardMostMove) {
8487                     /* Oops, gap.  How did that happen? */
8488                     return;
8489                 }
8490                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8491                 return;
8492             }
8493             gameInfo.result = moveType;
8494             p = strchr(yy_text, '{');
8495             if (p == NULL) p = strchr(yy_text, '(');
8496             if (p == NULL) {
8497                 p = yy_text;
8498                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8499             } else {
8500                 q = strchr(p, *p == '{' ? '}' : ')');
8501                 if (q != NULL) *q = NULLCHAR;
8502                 p++;
8503             }
8504             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8505             gameInfo.resultDetails = StrSave(p);
8506             continue;
8507         }
8508         if (boardIndex >= forwardMostMove &&
8509             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8510             backwardMostMove = blackPlaysFirst ? 1 : 0;
8511             return;
8512         }
8513         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8514                                  fromY, fromX, toY, toX, promoChar,
8515                                  parseList[boardIndex]);
8516         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8517         /* currentMoveString is set as a side-effect of yylex */
8518         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8519         strcat(moveList[boardIndex], "\n");
8520         boardIndex++;
8521         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8522         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8523           case MT_NONE:
8524           case MT_STALEMATE:
8525           default:
8526             break;
8527           case MT_CHECK:
8528             if(gameInfo.variant != VariantShogi)
8529                 strcat(parseList[boardIndex - 1], "+");
8530             break;
8531           case MT_CHECKMATE:
8532           case MT_STAINMATE:
8533             strcat(parseList[boardIndex - 1], "#");
8534             break;
8535         }
8536     }
8537 }
8538
8539
8540 /* Apply a move to the given board  */
8541 void
8542 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8543      int fromX, fromY, toX, toY;
8544      int promoChar;
8545      Board board;
8546 {
8547   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8548   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8549
8550     /* [HGM] compute & store e.p. status and castling rights for new position */
8551     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8552
8553       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8554       oldEP = (signed char)board[EP_STATUS];
8555       board[EP_STATUS] = EP_NONE;
8556
8557       if( board[toY][toX] != EmptySquare )
8558            board[EP_STATUS] = EP_CAPTURE;
8559
8560   if (fromY == DROP_RANK) {
8561         /* must be first */
8562         piece = board[toY][toX] = (ChessSquare) fromX;
8563   } else {
8564       int i;
8565
8566       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8567            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8568                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8569       } else
8570       if( board[fromY][fromX] == WhitePawn ) {
8571            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8572                board[EP_STATUS] = EP_PAWN_MOVE;
8573            if( toY-fromY==2) {
8574                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8575                         gameInfo.variant != VariantBerolina || toX < fromX)
8576                       board[EP_STATUS] = toX | berolina;
8577                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8578                         gameInfo.variant != VariantBerolina || toX > fromX)
8579                       board[EP_STATUS] = toX;
8580            }
8581       } else
8582       if( board[fromY][fromX] == BlackPawn ) {
8583            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8584                board[EP_STATUS] = EP_PAWN_MOVE;
8585            if( toY-fromY== -2) {
8586                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8587                         gameInfo.variant != VariantBerolina || toX < fromX)
8588                       board[EP_STATUS] = toX | berolina;
8589                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8590                         gameInfo.variant != VariantBerolina || toX > fromX)
8591                       board[EP_STATUS] = toX;
8592            }
8593        }
8594
8595        for(i=0; i<nrCastlingRights; i++) {
8596            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8597               board[CASTLING][i] == toX   && castlingRank[i] == toY
8598              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8599        }
8600
8601      if (fromX == toX && fromY == toY) return;
8602
8603      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8604      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8605      if(gameInfo.variant == VariantKnightmate)
8606          king += (int) WhiteUnicorn - (int) WhiteKing;
8607
8608     /* Code added by Tord: */
8609     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8610     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8611         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8612       board[fromY][fromX] = EmptySquare;
8613       board[toY][toX] = EmptySquare;
8614       if((toX > fromX) != (piece == WhiteRook)) {
8615         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8616       } else {
8617         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8618       }
8619     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8620                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8621       board[fromY][fromX] = EmptySquare;
8622       board[toY][toX] = EmptySquare;
8623       if((toX > fromX) != (piece == BlackRook)) {
8624         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8625       } else {
8626         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8627       }
8628     /* End of code added by Tord */
8629
8630     } else if (board[fromY][fromX] == king
8631         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8632         && toY == fromY && toX > fromX+1) {
8633         board[fromY][fromX] = EmptySquare;
8634         board[toY][toX] = king;
8635         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8636         board[fromY][BOARD_RGHT-1] = EmptySquare;
8637     } else if (board[fromY][fromX] == king
8638         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8639                && toY == fromY && toX < fromX-1) {
8640         board[fromY][fromX] = EmptySquare;
8641         board[toY][toX] = king;
8642         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8643         board[fromY][BOARD_LEFT] = EmptySquare;
8644     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8645                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8646                && toY >= BOARD_HEIGHT-promoRank
8647                ) {
8648         /* white pawn promotion */
8649         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8650         if (board[toY][toX] == EmptySquare) {
8651             board[toY][toX] = WhiteQueen;
8652         }
8653         if(gameInfo.variant==VariantBughouse ||
8654            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8655             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8656         board[fromY][fromX] = EmptySquare;
8657     } else if ((fromY == BOARD_HEIGHT-4)
8658                && (toX != fromX)
8659                && gameInfo.variant != VariantXiangqi
8660                && gameInfo.variant != VariantBerolina
8661                && (board[fromY][fromX] == WhitePawn)
8662                && (board[toY][toX] == EmptySquare)) {
8663         board[fromY][fromX] = EmptySquare;
8664         board[toY][toX] = WhitePawn;
8665         captured = board[toY - 1][toX];
8666         board[toY - 1][toX] = EmptySquare;
8667     } else if ((fromY == BOARD_HEIGHT-4)
8668                && (toX == fromX)
8669                && gameInfo.variant == VariantBerolina
8670                && (board[fromY][fromX] == WhitePawn)
8671                && (board[toY][toX] == EmptySquare)) {
8672         board[fromY][fromX] = EmptySquare;
8673         board[toY][toX] = WhitePawn;
8674         if(oldEP & EP_BEROLIN_A) {
8675                 captured = board[fromY][fromX-1];
8676                 board[fromY][fromX-1] = EmptySquare;
8677         }else{  captured = board[fromY][fromX+1];
8678                 board[fromY][fromX+1] = EmptySquare;
8679         }
8680     } else if (board[fromY][fromX] == king
8681         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8682                && toY == fromY && toX > fromX+1) {
8683         board[fromY][fromX] = EmptySquare;
8684         board[toY][toX] = king;
8685         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8686         board[fromY][BOARD_RGHT-1] = EmptySquare;
8687     } else if (board[fromY][fromX] == king
8688         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8689                && toY == fromY && toX < fromX-1) {
8690         board[fromY][fromX] = EmptySquare;
8691         board[toY][toX] = king;
8692         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8693         board[fromY][BOARD_LEFT] = EmptySquare;
8694     } else if (fromY == 7 && fromX == 3
8695                && board[fromY][fromX] == BlackKing
8696                && toY == 7 && toX == 5) {
8697         board[fromY][fromX] = EmptySquare;
8698         board[toY][toX] = BlackKing;
8699         board[fromY][7] = EmptySquare;
8700         board[toY][4] = BlackRook;
8701     } else if (fromY == 7 && fromX == 3
8702                && board[fromY][fromX] == BlackKing
8703                && toY == 7 && toX == 1) {
8704         board[fromY][fromX] = EmptySquare;
8705         board[toY][toX] = BlackKing;
8706         board[fromY][0] = EmptySquare;
8707         board[toY][2] = BlackRook;
8708     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8709                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8710                && toY < promoRank
8711                ) {
8712         /* black pawn promotion */
8713         board[toY][toX] = CharToPiece(ToLower(promoChar));
8714         if (board[toY][toX] == EmptySquare) {
8715             board[toY][toX] = BlackQueen;
8716         }
8717         if(gameInfo.variant==VariantBughouse ||
8718            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8719             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8720         board[fromY][fromX] = EmptySquare;
8721     } else if ((fromY == 3)
8722                && (toX != fromX)
8723                && gameInfo.variant != VariantXiangqi
8724                && gameInfo.variant != VariantBerolina
8725                && (board[fromY][fromX] == BlackPawn)
8726                && (board[toY][toX] == EmptySquare)) {
8727         board[fromY][fromX] = EmptySquare;
8728         board[toY][toX] = BlackPawn;
8729         captured = board[toY + 1][toX];
8730         board[toY + 1][toX] = EmptySquare;
8731     } else if ((fromY == 3)
8732                && (toX == fromX)
8733                && gameInfo.variant == VariantBerolina
8734                && (board[fromY][fromX] == BlackPawn)
8735                && (board[toY][toX] == EmptySquare)) {
8736         board[fromY][fromX] = EmptySquare;
8737         board[toY][toX] = BlackPawn;
8738         if(oldEP & EP_BEROLIN_A) {
8739                 captured = board[fromY][fromX-1];
8740                 board[fromY][fromX-1] = EmptySquare;
8741         }else{  captured = board[fromY][fromX+1];
8742                 board[fromY][fromX+1] = EmptySquare;
8743         }
8744     } else {
8745         board[toY][toX] = board[fromY][fromX];
8746         board[fromY][fromX] = EmptySquare;
8747     }
8748   }
8749
8750     if (gameInfo.holdingsWidth != 0) {
8751
8752       /* !!A lot more code needs to be written to support holdings  */
8753       /* [HGM] OK, so I have written it. Holdings are stored in the */
8754       /* penultimate board files, so they are automaticlly stored   */
8755       /* in the game history.                                       */
8756       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8757                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8758         /* Delete from holdings, by decreasing count */
8759         /* and erasing image if necessary            */
8760         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8761         if(p < (int) BlackPawn) { /* white drop */
8762              p -= (int)WhitePawn;
8763                  p = PieceToNumber((ChessSquare)p);
8764              if(p >= gameInfo.holdingsSize) p = 0;
8765              if(--board[p][BOARD_WIDTH-2] <= 0)
8766                   board[p][BOARD_WIDTH-1] = EmptySquare;
8767              if((int)board[p][BOARD_WIDTH-2] < 0)
8768                         board[p][BOARD_WIDTH-2] = 0;
8769         } else {                  /* black drop */
8770              p -= (int)BlackPawn;
8771                  p = PieceToNumber((ChessSquare)p);
8772              if(p >= gameInfo.holdingsSize) p = 0;
8773              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8774                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8775              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8776                         board[BOARD_HEIGHT-1-p][1] = 0;
8777         }
8778       }
8779       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8780           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8781         /* [HGM] holdings: Add to holdings, if holdings exist */
8782         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8783                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8784                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8785         }
8786         p = (int) captured;
8787         if (p >= (int) BlackPawn) {
8788           p -= (int)BlackPawn;
8789           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8790                   /* in Shogi restore piece to its original  first */
8791                   captured = (ChessSquare) (DEMOTED captured);
8792                   p = DEMOTED p;
8793           }
8794           p = PieceToNumber((ChessSquare)p);
8795           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8796           board[p][BOARD_WIDTH-2]++;
8797           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8798         } else {
8799           p -= (int)WhitePawn;
8800           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8801                   captured = (ChessSquare) (DEMOTED captured);
8802                   p = DEMOTED p;
8803           }
8804           p = PieceToNumber((ChessSquare)p);
8805           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8806           board[BOARD_HEIGHT-1-p][1]++;
8807           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8808         }
8809       }
8810     } else if (gameInfo.variant == VariantAtomic) {
8811       if (captured != EmptySquare) {
8812         int y, x;
8813         for (y = toY-1; y <= toY+1; y++) {
8814           for (x = toX-1; x <= toX+1; x++) {
8815             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8816                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8817               board[y][x] = EmptySquare;
8818             }
8819           }
8820         }
8821         board[toY][toX] = EmptySquare;
8822       }
8823     }
8824     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8825         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8826     } else
8827     if(promoChar == '+') {
8828         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8829         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8830     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8831         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8832     }
8833     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8834                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8835         // [HGM] superchess: take promotion piece out of holdings
8836         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8837         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8838             if(!--board[k][BOARD_WIDTH-2])
8839                 board[k][BOARD_WIDTH-1] = EmptySquare;
8840         } else {
8841             if(!--board[BOARD_HEIGHT-1-k][1])
8842                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8843         }
8844     }
8845
8846 }
8847
8848 /* Updates forwardMostMove */
8849 void
8850 MakeMove(fromX, fromY, toX, toY, promoChar)
8851      int fromX, fromY, toX, toY;
8852      int promoChar;
8853 {
8854 //    forwardMostMove++; // [HGM] bare: moved downstream
8855
8856     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8857         int timeLeft; static int lastLoadFlag=0; int king, piece;
8858         piece = boards[forwardMostMove][fromY][fromX];
8859         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8860         if(gameInfo.variant == VariantKnightmate)
8861             king += (int) WhiteUnicorn - (int) WhiteKing;
8862         if(forwardMostMove == 0) {
8863             if(blackPlaysFirst)
8864                 fprintf(serverMoves, "%s;", second.tidy);
8865             fprintf(serverMoves, "%s;", first.tidy);
8866             if(!blackPlaysFirst)
8867                 fprintf(serverMoves, "%s;", second.tidy);
8868         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8869         lastLoadFlag = loadFlag;
8870         // print base move
8871         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8872         // print castling suffix
8873         if( toY == fromY && piece == king ) {
8874             if(toX-fromX > 1)
8875                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8876             if(fromX-toX >1)
8877                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8878         }
8879         // e.p. suffix
8880         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8881              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8882              boards[forwardMostMove][toY][toX] == EmptySquare
8883              && fromX != toX && fromY != toY)
8884                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8885         // promotion suffix
8886         if(promoChar != NULLCHAR)
8887                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8888         if(!loadFlag) {
8889             fprintf(serverMoves, "/%d/%d",
8890                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8891             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8892             else                      timeLeft = blackTimeRemaining/1000;
8893             fprintf(serverMoves, "/%d", timeLeft);
8894         }
8895         fflush(serverMoves);
8896     }
8897
8898     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8899       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8900                         0, 1);
8901       return;
8902     }
8903     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8904     if (commentList[forwardMostMove+1] != NULL) {
8905         free(commentList[forwardMostMove+1]);
8906         commentList[forwardMostMove+1] = NULL;
8907     }
8908     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8909     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8910     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8911     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8912     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8913     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8914     gameInfo.result = GameUnfinished;
8915     if (gameInfo.resultDetails != NULL) {
8916         free(gameInfo.resultDetails);
8917         gameInfo.resultDetails = NULL;
8918     }
8919     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8920                               moveList[forwardMostMove - 1]);
8921     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8922                              PosFlags(forwardMostMove - 1),
8923                              fromY, fromX, toY, toX, promoChar,
8924                              parseList[forwardMostMove - 1]);
8925     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8926       case MT_NONE:
8927       case MT_STALEMATE:
8928       default:
8929         break;
8930       case MT_CHECK:
8931         if(gameInfo.variant != VariantShogi)
8932             strcat(parseList[forwardMostMove - 1], "+");
8933         break;
8934       case MT_CHECKMATE:
8935       case MT_STAINMATE:
8936         strcat(parseList[forwardMostMove - 1], "#");
8937         break;
8938     }
8939     if (appData.debugMode) {
8940         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8941     }
8942
8943 }
8944
8945 /* Updates currentMove if not pausing */
8946 void
8947 ShowMove(fromX, fromY, toX, toY)
8948 {
8949     int instant = (gameMode == PlayFromGameFile) ?
8950         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8951     if(appData.noGUI) return;
8952     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8953         if (!instant) {
8954             if (forwardMostMove == currentMove + 1) {
8955                 AnimateMove(boards[forwardMostMove - 1],
8956                             fromX, fromY, toX, toY);
8957             }
8958             if (appData.highlightLastMove) {
8959                 SetHighlights(fromX, fromY, toX, toY);
8960             }
8961         }
8962         currentMove = forwardMostMove;
8963     }
8964
8965     if (instant) return;
8966
8967     DisplayMove(currentMove - 1);
8968     DrawPosition(FALSE, boards[currentMove]);
8969     DisplayBothClocks();
8970     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8971 }
8972
8973 void SendEgtPath(ChessProgramState *cps)
8974 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8975         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8976
8977         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8978
8979         while(*p) {
8980             char c, *q = name+1, *r, *s;
8981
8982             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8983             while(*p && *p != ',') *q++ = *p++;
8984             *q++ = ':'; *q = 0;
8985             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8986                 strcmp(name, ",nalimov:") == 0 ) {
8987                 // take nalimov path from the menu-changeable option first, if it is defined
8988               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8989                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8990             } else
8991             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8992                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8993                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8994                 s = r = StrStr(s, ":") + 1; // beginning of path info
8995                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8996                 c = *r; *r = 0;             // temporarily null-terminate path info
8997                     *--q = 0;               // strip of trailig ':' from name
8998                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8999                 *r = c;
9000                 SendToProgram(buf,cps);     // send egtbpath command for this format
9001             }
9002             if(*p == ',') p++; // read away comma to position for next format name
9003         }
9004 }
9005
9006 void
9007 InitChessProgram(cps, setup)
9008      ChessProgramState *cps;
9009      int setup; /* [HGM] needed to setup FRC opening position */
9010 {
9011     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9012     if (appData.noChessProgram) return;
9013     hintRequested = FALSE;
9014     bookRequested = FALSE;
9015
9016     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9017     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9018     if(cps->memSize) { /* [HGM] memory */
9019       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9020         SendToProgram(buf, cps);
9021     }
9022     SendEgtPath(cps); /* [HGM] EGT */
9023     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9024       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9025         SendToProgram(buf, cps);
9026     }
9027
9028     SendToProgram(cps->initString, cps);
9029     if (gameInfo.variant != VariantNormal &&
9030         gameInfo.variant != VariantLoadable
9031         /* [HGM] also send variant if board size non-standard */
9032         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9033                                             ) {
9034       char *v = VariantName(gameInfo.variant);
9035       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9036         /* [HGM] in protocol 1 we have to assume all variants valid */
9037         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9038         DisplayFatalError(buf, 0, 1);
9039         return;
9040       }
9041
9042       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9043       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9044       if( gameInfo.variant == VariantXiangqi )
9045            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9046       if( gameInfo.variant == VariantShogi )
9047            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9048       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9049            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9050       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9051           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9052            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9053       if( gameInfo.variant == VariantCourier )
9054            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9055       if( gameInfo.variant == VariantSuper )
9056            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9057       if( gameInfo.variant == VariantGreat )
9058            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9059       if( gameInfo.variant == VariantSChess )
9060            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9061
9062       if(overruled) {
9063         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9064                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9065            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9066            if(StrStr(cps->variants, b) == NULL) {
9067                // specific sized variant not known, check if general sizing allowed
9068                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9069                    if(StrStr(cps->variants, "boardsize") == NULL) {
9070                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9071                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9072                        DisplayFatalError(buf, 0, 1);
9073                        return;
9074                    }
9075                    /* [HGM] here we really should compare with the maximum supported board size */
9076                }
9077            }
9078       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9079       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9080       SendToProgram(buf, cps);
9081     }
9082     currentlyInitializedVariant = gameInfo.variant;
9083
9084     /* [HGM] send opening position in FRC to first engine */
9085     if(setup) {
9086           SendToProgram("force\n", cps);
9087           SendBoard(cps, 0);
9088           /* engine is now in force mode! Set flag to wake it up after first move. */
9089           setboardSpoiledMachineBlack = 1;
9090     }
9091
9092     if (cps->sendICS) {
9093       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9094       SendToProgram(buf, cps);
9095     }
9096     cps->maybeThinking = FALSE;
9097     cps->offeredDraw = 0;
9098     if (!appData.icsActive) {
9099         SendTimeControl(cps, movesPerSession, timeControl,
9100                         timeIncrement, appData.searchDepth,
9101                         searchTime);
9102     }
9103     if (appData.showThinking
9104         // [HGM] thinking: four options require thinking output to be sent
9105         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9106                                 ) {
9107         SendToProgram("post\n", cps);
9108     }
9109     SendToProgram("hard\n", cps);
9110     if (!appData.ponderNextMove) {
9111         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9112            it without being sure what state we are in first.  "hard"
9113            is not a toggle, so that one is OK.
9114          */
9115         SendToProgram("easy\n", cps);
9116     }
9117     if (cps->usePing) {
9118       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9119       SendToProgram(buf, cps);
9120     }
9121     cps->initDone = TRUE;
9122 }
9123
9124
9125 void
9126 StartChessProgram(cps)
9127      ChessProgramState *cps;
9128 {
9129     char buf[MSG_SIZ];
9130     int err;
9131
9132     if (appData.noChessProgram) return;
9133     cps->initDone = FALSE;
9134
9135     if (strcmp(cps->host, "localhost") == 0) {
9136         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9137     } else if (*appData.remoteShell == NULLCHAR) {
9138         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9139     } else {
9140         if (*appData.remoteUser == NULLCHAR) {
9141           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9142                     cps->program);
9143         } else {
9144           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9145                     cps->host, appData.remoteUser, cps->program);
9146         }
9147         err = StartChildProcess(buf, "", &cps->pr);
9148     }
9149
9150     if (err != 0) {
9151       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9152         DisplayFatalError(buf, err, 1);
9153         cps->pr = NoProc;
9154         cps->isr = NULL;
9155         return;
9156     }
9157
9158     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9159     if (cps->protocolVersion > 1) {
9160       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9161       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9162       cps->comboCnt = 0;  //                and values of combo boxes
9163       SendToProgram(buf, cps);
9164     } else {
9165       SendToProgram("xboard\n", cps);
9166     }
9167 }
9168
9169
9170 void
9171 TwoMachinesEventIfReady P((void))
9172 {
9173   if (first.lastPing != first.lastPong) {
9174     DisplayMessage("", _("Waiting for first chess program"));
9175     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9176     return;
9177   }
9178   if (second.lastPing != second.lastPong) {
9179     DisplayMessage("", _("Waiting for second chess program"));
9180     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9181     return;
9182   }
9183   ThawUI();
9184   TwoMachinesEvent();
9185 }
9186
9187 void
9188 NextMatchGame P((void))
9189 {
9190     int index; /* [HGM] autoinc: step load index during match */
9191     Reset(FALSE, TRUE);
9192     if (*appData.loadGameFile != NULLCHAR) {
9193         index = appData.loadGameIndex;
9194         if(index < 0) { // [HGM] autoinc
9195             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9196             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9197         }
9198         LoadGameFromFile(appData.loadGameFile,
9199                          index,
9200                          appData.loadGameFile, FALSE);
9201     } else if (*appData.loadPositionFile != NULLCHAR) {
9202         index = appData.loadPositionIndex;
9203         if(index < 0) { // [HGM] autoinc
9204             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9205             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9206         }
9207         LoadPositionFromFile(appData.loadPositionFile,
9208                              index,
9209                              appData.loadPositionFile);
9210     }
9211     TwoMachinesEventIfReady();
9212 }
9213
9214 void UserAdjudicationEvent( int result )
9215 {
9216     ChessMove gameResult = GameIsDrawn;
9217
9218     if( result > 0 ) {
9219         gameResult = WhiteWins;
9220     }
9221     else if( result < 0 ) {
9222         gameResult = BlackWins;
9223     }
9224
9225     if( gameMode == TwoMachinesPlay ) {
9226         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9227     }
9228 }
9229
9230
9231 // [HGM] save: calculate checksum of game to make games easily identifiable
9232 int StringCheckSum(char *s)
9233 {
9234         int i = 0;
9235         if(s==NULL) return 0;
9236         while(*s) i = i*259 + *s++;
9237         return i;
9238 }
9239
9240 int GameCheckSum()
9241 {
9242         int i, sum=0;
9243         for(i=backwardMostMove; i<forwardMostMove; i++) {
9244                 sum += pvInfoList[i].depth;
9245                 sum += StringCheckSum(parseList[i]);
9246                 sum += StringCheckSum(commentList[i]);
9247                 sum *= 261;
9248         }
9249         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9250         return sum + StringCheckSum(commentList[i]);
9251 } // end of save patch
9252
9253 void
9254 GameEnds(result, resultDetails, whosays)
9255      ChessMove result;
9256      char *resultDetails;
9257      int whosays;
9258 {
9259     GameMode nextGameMode;
9260     int isIcsGame;
9261     char buf[MSG_SIZ], popupRequested = 0;
9262
9263     if(endingGame) return; /* [HGM] crash: forbid recursion */
9264     endingGame = 1;
9265     if(twoBoards) { // [HGM] dual: switch back to one board
9266         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9267         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9268     }
9269     if (appData.debugMode) {
9270       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9271               result, resultDetails ? resultDetails : "(null)", whosays);
9272     }
9273
9274     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9275
9276     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9277         /* If we are playing on ICS, the server decides when the
9278            game is over, but the engine can offer to draw, claim
9279            a draw, or resign.
9280          */
9281 #if ZIPPY
9282         if (appData.zippyPlay && first.initDone) {
9283             if (result == GameIsDrawn) {
9284                 /* In case draw still needs to be claimed */
9285                 SendToICS(ics_prefix);
9286                 SendToICS("draw\n");
9287             } else if (StrCaseStr(resultDetails, "resign")) {
9288                 SendToICS(ics_prefix);
9289                 SendToICS("resign\n");
9290             }
9291         }
9292 #endif
9293         endingGame = 0; /* [HGM] crash */
9294         return;
9295     }
9296
9297     /* If we're loading the game from a file, stop */
9298     if (whosays == GE_FILE) {
9299       (void) StopLoadGameTimer();
9300       gameFileFP = NULL;
9301     }
9302
9303     /* Cancel draw offers */
9304     first.offeredDraw = second.offeredDraw = 0;
9305
9306     /* If this is an ICS game, only ICS can really say it's done;
9307        if not, anyone can. */
9308     isIcsGame = (gameMode == IcsPlayingWhite ||
9309                  gameMode == IcsPlayingBlack ||
9310                  gameMode == IcsObserving    ||
9311                  gameMode == IcsExamining);
9312
9313     if (!isIcsGame || whosays == GE_ICS) {
9314         /* OK -- not an ICS game, or ICS said it was done */
9315         StopClocks();
9316         if (!isIcsGame && !appData.noChessProgram)
9317           SetUserThinkingEnables();
9318
9319         /* [HGM] if a machine claims the game end we verify this claim */
9320         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9321             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9322                 char claimer;
9323                 ChessMove trueResult = (ChessMove) -1;
9324
9325                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9326                                             first.twoMachinesColor[0] :
9327                                             second.twoMachinesColor[0] ;
9328
9329                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9330                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9331                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9332                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9333                 } else
9334                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9335                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9336                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9337                 } else
9338                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9339                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9340                 }
9341
9342                 // now verify win claims, but not in drop games, as we don't understand those yet
9343                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9344                                                  || gameInfo.variant == VariantGreat) &&
9345                     (result == WhiteWins && claimer == 'w' ||
9346                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9347                       if (appData.debugMode) {
9348                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9349                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9350                       }
9351                       if(result != trueResult) {
9352                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9353                               result = claimer == 'w' ? BlackWins : WhiteWins;
9354                               resultDetails = buf;
9355                       }
9356                 } else
9357                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9358                     && (forwardMostMove <= backwardMostMove ||
9359                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9360                         (claimer=='b')==(forwardMostMove&1))
9361                                                                                   ) {
9362                       /* [HGM] verify: draws that were not flagged are false claims */
9363                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9364                       result = claimer == 'w' ? BlackWins : WhiteWins;
9365                       resultDetails = buf;
9366                 }
9367                 /* (Claiming a loss is accepted no questions asked!) */
9368             }
9369             /* [HGM] bare: don't allow bare King to win */
9370             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9371                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9372                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9373                && result != GameIsDrawn)
9374             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9375                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9376                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9377                         if(p >= 0 && p <= (int)WhiteKing) k++;
9378                 }
9379                 if (appData.debugMode) {
9380                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9381                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9382                 }
9383                 if(k <= 1) {
9384                         result = GameIsDrawn;
9385                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9386                         resultDetails = buf;
9387                 }
9388             }
9389         }
9390
9391
9392         if(serverMoves != NULL && !loadFlag) { char c = '=';
9393             if(result==WhiteWins) c = '+';
9394             if(result==BlackWins) c = '-';
9395             if(resultDetails != NULL)
9396                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9397         }
9398         if (resultDetails != NULL) {
9399             gameInfo.result = result;
9400             gameInfo.resultDetails = StrSave(resultDetails);
9401
9402             /* display last move only if game was not loaded from file */
9403             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9404                 DisplayMove(currentMove - 1);
9405
9406             if (forwardMostMove != 0) {
9407                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9408                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9409                                                                 ) {
9410                     if (*appData.saveGameFile != NULLCHAR) {
9411                         SaveGameToFile(appData.saveGameFile, TRUE);
9412                     } else if (appData.autoSaveGames) {
9413                         AutoSaveGame();
9414                     }
9415                     if (*appData.savePositionFile != NULLCHAR) {
9416                         SavePositionToFile(appData.savePositionFile);
9417                     }
9418                 }
9419             }
9420
9421             /* Tell program how game ended in case it is learning */
9422             /* [HGM] Moved this to after saving the PGN, just in case */
9423             /* engine died and we got here through time loss. In that */
9424             /* case we will get a fatal error writing the pipe, which */
9425             /* would otherwise lose us the PGN.                       */
9426             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9427             /* output during GameEnds should never be fatal anymore   */
9428             if (gameMode == MachinePlaysWhite ||
9429                 gameMode == MachinePlaysBlack ||
9430                 gameMode == TwoMachinesPlay ||
9431                 gameMode == IcsPlayingWhite ||
9432                 gameMode == IcsPlayingBlack ||
9433                 gameMode == BeginningOfGame) {
9434                 char buf[MSG_SIZ];
9435                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9436                         resultDetails);
9437                 if (first.pr != NoProc) {
9438                     SendToProgram(buf, &first);
9439                 }
9440                 if (second.pr != NoProc &&
9441                     gameMode == TwoMachinesPlay) {
9442                     SendToProgram(buf, &second);
9443                 }
9444             }
9445         }
9446
9447         if (appData.icsActive) {
9448             if (appData.quietPlay &&
9449                 (gameMode == IcsPlayingWhite ||
9450                  gameMode == IcsPlayingBlack)) {
9451                 SendToICS(ics_prefix);
9452                 SendToICS("set shout 1\n");
9453             }
9454             nextGameMode = IcsIdle;
9455             ics_user_moved = FALSE;
9456             /* clean up premove.  It's ugly when the game has ended and the
9457              * premove highlights are still on the board.
9458              */
9459             if (gotPremove) {
9460               gotPremove = FALSE;
9461               ClearPremoveHighlights();
9462               DrawPosition(FALSE, boards[currentMove]);
9463             }
9464             if (whosays == GE_ICS) {
9465                 switch (result) {
9466                 case WhiteWins:
9467                     if (gameMode == IcsPlayingWhite)
9468                         PlayIcsWinSound();
9469                     else if(gameMode == IcsPlayingBlack)
9470                         PlayIcsLossSound();
9471                     break;
9472                 case BlackWins:
9473                     if (gameMode == IcsPlayingBlack)
9474                         PlayIcsWinSound();
9475                     else if(gameMode == IcsPlayingWhite)
9476                         PlayIcsLossSound();
9477                     break;
9478                 case GameIsDrawn:
9479                     PlayIcsDrawSound();
9480                     break;
9481                 default:
9482                     PlayIcsUnfinishedSound();
9483                 }
9484             }
9485         } else if (gameMode == EditGame ||
9486                    gameMode == PlayFromGameFile ||
9487                    gameMode == AnalyzeMode ||
9488                    gameMode == AnalyzeFile) {
9489             nextGameMode = gameMode;
9490         } else {
9491             nextGameMode = EndOfGame;
9492         }
9493         pausing = FALSE;
9494         ModeHighlight();
9495     } else {
9496         nextGameMode = gameMode;
9497     }
9498
9499     if (appData.noChessProgram) {
9500         gameMode = nextGameMode;
9501         ModeHighlight();
9502         endingGame = 0; /* [HGM] crash */
9503         return;
9504     }
9505
9506     if (first.reuse) {
9507         /* Put first chess program into idle state */
9508         if (first.pr != NoProc &&
9509             (gameMode == MachinePlaysWhite ||
9510              gameMode == MachinePlaysBlack ||
9511              gameMode == TwoMachinesPlay ||
9512              gameMode == IcsPlayingWhite ||
9513              gameMode == IcsPlayingBlack ||
9514              gameMode == BeginningOfGame)) {
9515             SendToProgram("force\n", &first);
9516             if (first.usePing) {
9517               char buf[MSG_SIZ];
9518               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9519               SendToProgram(buf, &first);
9520             }
9521         }
9522     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9523         /* Kill off first chess program */
9524         if (first.isr != NULL)
9525           RemoveInputSource(first.isr);
9526         first.isr = NULL;
9527
9528         if (first.pr != NoProc) {
9529             ExitAnalyzeMode();
9530             DoSleep( appData.delayBeforeQuit );
9531             SendToProgram("quit\n", &first);
9532             DoSleep( appData.delayAfterQuit );
9533             DestroyChildProcess(first.pr, first.useSigterm);
9534         }
9535         first.pr = NoProc;
9536     }
9537     if (second.reuse) {
9538         /* Put second chess program into idle state */
9539         if (second.pr != NoProc &&
9540             gameMode == TwoMachinesPlay) {
9541             SendToProgram("force\n", &second);
9542             if (second.usePing) {
9543               char buf[MSG_SIZ];
9544               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9545               SendToProgram(buf, &second);
9546             }
9547         }
9548     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9549         /* Kill off second chess program */
9550         if (second.isr != NULL)
9551           RemoveInputSource(second.isr);
9552         second.isr = NULL;
9553
9554         if (second.pr != NoProc) {
9555             DoSleep( appData.delayBeforeQuit );
9556             SendToProgram("quit\n", &second);
9557             DoSleep( appData.delayAfterQuit );
9558             DestroyChildProcess(second.pr, second.useSigterm);
9559         }
9560         second.pr = NoProc;
9561     }
9562
9563     if (matchMode && gameMode == TwoMachinesPlay) {
9564         switch (result) {
9565         case WhiteWins:
9566           if (first.twoMachinesColor[0] == 'w') {
9567             first.matchWins++;
9568           } else {
9569             second.matchWins++;
9570           }
9571           break;
9572         case BlackWins:
9573           if (first.twoMachinesColor[0] == 'b') {
9574             first.matchWins++;
9575           } else {
9576             second.matchWins++;
9577           }
9578           break;
9579         default:
9580           break;
9581         }
9582         if (matchGame < appData.matchGames) {
9583             char *tmp;
9584             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9585                 tmp = first.twoMachinesColor;
9586                 first.twoMachinesColor = second.twoMachinesColor;
9587                 second.twoMachinesColor = tmp;
9588             }
9589             gameMode = nextGameMode;
9590             matchGame++;
9591             if(appData.matchPause>10000 || appData.matchPause<10)
9592                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9593             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9594             endingGame = 0; /* [HGM] crash */
9595             return;
9596         } else {
9597             gameMode = nextGameMode;
9598             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9599                      first.tidy, second.tidy,
9600                      first.matchWins, second.matchWins,
9601                      appData.matchGames - (first.matchWins + second.matchWins));
9602             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9603             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9604                 first.twoMachinesColor = "black\n";
9605                 second.twoMachinesColor = "white\n";
9606             } else {
9607                 first.twoMachinesColor = "white\n";
9608                 second.twoMachinesColor = "black\n";
9609             }
9610         }
9611     }
9612     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9613         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9614       ExitAnalyzeMode();
9615     gameMode = nextGameMode;
9616     ModeHighlight();
9617     endingGame = 0;  /* [HGM] crash */
9618     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9619       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9620         matchMode = FALSE; appData.matchGames = matchGame = 0;
9621         DisplayNote(buf);
9622       }
9623     }
9624 }
9625
9626 /* Assumes program was just initialized (initString sent).
9627    Leaves program in force mode. */
9628 void
9629 FeedMovesToProgram(cps, upto)
9630      ChessProgramState *cps;
9631      int upto;
9632 {
9633     int i;
9634
9635     if (appData.debugMode)
9636       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9637               startedFromSetupPosition ? "position and " : "",
9638               backwardMostMove, upto, cps->which);
9639     if(currentlyInitializedVariant != gameInfo.variant) {
9640       char buf[MSG_SIZ];
9641         // [HGM] variantswitch: make engine aware of new variant
9642         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9643                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9644         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9645         SendToProgram(buf, cps);
9646         currentlyInitializedVariant = gameInfo.variant;
9647     }
9648     SendToProgram("force\n", cps);
9649     if (startedFromSetupPosition) {
9650         SendBoard(cps, backwardMostMove);
9651     if (appData.debugMode) {
9652         fprintf(debugFP, "feedMoves\n");
9653     }
9654     }
9655     for (i = backwardMostMove; i < upto; i++) {
9656         SendMoveToProgram(i, cps);
9657     }
9658 }
9659
9660
9661 void
9662 ResurrectChessProgram()
9663 {
9664      /* The chess program may have exited.
9665         If so, restart it and feed it all the moves made so far. */
9666
9667     if (appData.noChessProgram || first.pr != NoProc) return;
9668
9669     StartChessProgram(&first);
9670     InitChessProgram(&first, FALSE);
9671     FeedMovesToProgram(&first, currentMove);
9672
9673     if (!first.sendTime) {
9674         /* can't tell gnuchess what its clock should read,
9675            so we bow to its notion. */
9676         ResetClocks();
9677         timeRemaining[0][currentMove] = whiteTimeRemaining;
9678         timeRemaining[1][currentMove] = blackTimeRemaining;
9679     }
9680
9681     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9682                 appData.icsEngineAnalyze) && first.analysisSupport) {
9683       SendToProgram("analyze\n", &first);
9684       first.analyzing = TRUE;
9685     }
9686 }
9687
9688 /*
9689  * Button procedures
9690  */
9691 void
9692 Reset(redraw, init)
9693      int redraw, init;
9694 {
9695     int i;
9696
9697     if (appData.debugMode) {
9698         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9699                 redraw, init, gameMode);
9700     }
9701     CleanupTail(); // [HGM] vari: delete any stored variations
9702     pausing = pauseExamInvalid = FALSE;
9703     startedFromSetupPosition = blackPlaysFirst = FALSE;
9704     firstMove = TRUE;
9705     whiteFlag = blackFlag = FALSE;
9706     userOfferedDraw = FALSE;
9707     hintRequested = bookRequested = FALSE;
9708     first.maybeThinking = FALSE;
9709     second.maybeThinking = FALSE;
9710     first.bookSuspend = FALSE; // [HGM] book
9711     second.bookSuspend = FALSE;
9712     thinkOutput[0] = NULLCHAR;
9713     lastHint[0] = NULLCHAR;
9714     ClearGameInfo(&gameInfo);
9715     gameInfo.variant = StringToVariant(appData.variant);
9716     ics_user_moved = ics_clock_paused = FALSE;
9717     ics_getting_history = H_FALSE;
9718     ics_gamenum = -1;
9719     white_holding[0] = black_holding[0] = NULLCHAR;
9720     ClearProgramStats();
9721     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9722
9723     ResetFrontEnd();
9724     ClearHighlights();
9725     flipView = appData.flipView;
9726     ClearPremoveHighlights();
9727     gotPremove = FALSE;
9728     alarmSounded = FALSE;
9729
9730     GameEnds(EndOfFile, NULL, GE_PLAYER);
9731     if(appData.serverMovesName != NULL) {
9732         /* [HGM] prepare to make moves file for broadcasting */
9733         clock_t t = clock();
9734         if(serverMoves != NULL) fclose(serverMoves);
9735         serverMoves = fopen(appData.serverMovesName, "r");
9736         if(serverMoves != NULL) {
9737             fclose(serverMoves);
9738             /* delay 15 sec before overwriting, so all clients can see end */
9739             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9740         }
9741         serverMoves = fopen(appData.serverMovesName, "w");
9742     }
9743
9744     ExitAnalyzeMode();
9745     gameMode = BeginningOfGame;
9746     ModeHighlight();
9747     if(appData.icsActive) gameInfo.variant = VariantNormal;
9748     currentMove = forwardMostMove = backwardMostMove = 0;
9749     InitPosition(redraw);
9750     for (i = 0; i < MAX_MOVES; i++) {
9751         if (commentList[i] != NULL) {
9752             free(commentList[i]);
9753             commentList[i] = NULL;
9754         }
9755     }
9756     ResetClocks();
9757     timeRemaining[0][0] = whiteTimeRemaining;
9758     timeRemaining[1][0] = blackTimeRemaining;
9759     if (first.pr == NULL) {
9760         StartChessProgram(&first);
9761     }
9762     if (init) {
9763             InitChessProgram(&first, startedFromSetupPosition);
9764     }
9765     DisplayTitle("");
9766     DisplayMessage("", "");
9767     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9768     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9769 }
9770
9771 void
9772 AutoPlayGameLoop()
9773 {
9774     for (;;) {
9775         if (!AutoPlayOneMove())
9776           return;
9777         if (matchMode || appData.timeDelay == 0)
9778           continue;
9779         if (appData.timeDelay < 0)
9780           return;
9781         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9782         break;
9783     }
9784 }
9785
9786
9787 int
9788 AutoPlayOneMove()
9789 {
9790     int fromX, fromY, toX, toY;
9791
9792     if (appData.debugMode) {
9793       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9794     }
9795
9796     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9797       return FALSE;
9798
9799     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9800       pvInfoList[currentMove].depth = programStats.depth;
9801       pvInfoList[currentMove].score = programStats.score;
9802       pvInfoList[currentMove].time  = 0;
9803       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9804     }
9805
9806     if (currentMove >= forwardMostMove) {
9807       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9808       gameMode = EditGame;
9809       ModeHighlight();
9810
9811       /* [AS] Clear current move marker at the end of a game */
9812       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9813
9814       return FALSE;
9815     }
9816
9817     toX = moveList[currentMove][2] - AAA;
9818     toY = moveList[currentMove][3] - ONE;
9819
9820     if (moveList[currentMove][1] == '@') {
9821         if (appData.highlightLastMove) {
9822             SetHighlights(-1, -1, toX, toY);
9823         }
9824     } else {
9825         fromX = moveList[currentMove][0] - AAA;
9826         fromY = moveList[currentMove][1] - ONE;
9827
9828         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9829
9830         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9831
9832         if (appData.highlightLastMove) {
9833             SetHighlights(fromX, fromY, toX, toY);
9834         }
9835     }
9836     DisplayMove(currentMove);
9837     SendMoveToProgram(currentMove++, &first);
9838     DisplayBothClocks();
9839     DrawPosition(FALSE, boards[currentMove]);
9840     // [HGM] PV info: always display, routine tests if empty
9841     DisplayComment(currentMove - 1, commentList[currentMove]);
9842     return TRUE;
9843 }
9844
9845
9846 int
9847 LoadGameOneMove(readAhead)
9848      ChessMove readAhead;
9849 {
9850     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9851     char promoChar = NULLCHAR;
9852     ChessMove moveType;
9853     char move[MSG_SIZ];
9854     char *p, *q;
9855
9856     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9857         gameMode != AnalyzeMode && gameMode != Training) {
9858         gameFileFP = NULL;
9859         return FALSE;
9860     }
9861
9862     yyboardindex = forwardMostMove;
9863     if (readAhead != EndOfFile) {
9864       moveType = readAhead;
9865     } else {
9866       if (gameFileFP == NULL)
9867           return FALSE;
9868       moveType = (ChessMove) Myylex();
9869     }
9870
9871     done = FALSE;
9872     switch (moveType) {
9873       case Comment:
9874         if (appData.debugMode)
9875           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9876         p = yy_text;
9877
9878         /* append the comment but don't display it */
9879         AppendComment(currentMove, p, FALSE);
9880         return TRUE;
9881
9882       case WhiteCapturesEnPassant:
9883       case BlackCapturesEnPassant:
9884       case WhitePromotion:
9885       case BlackPromotion:
9886       case WhiteNonPromotion:
9887       case BlackNonPromotion:
9888       case NormalMove:
9889       case WhiteKingSideCastle:
9890       case WhiteQueenSideCastle:
9891       case BlackKingSideCastle:
9892       case BlackQueenSideCastle:
9893       case WhiteKingSideCastleWild:
9894       case WhiteQueenSideCastleWild:
9895       case BlackKingSideCastleWild:
9896       case BlackQueenSideCastleWild:
9897       /* PUSH Fabien */
9898       case WhiteHSideCastleFR:
9899       case WhiteASideCastleFR:
9900       case BlackHSideCastleFR:
9901       case BlackASideCastleFR:
9902       /* POP Fabien */
9903         if (appData.debugMode)
9904           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9905         fromX = currentMoveString[0] - AAA;
9906         fromY = currentMoveString[1] - ONE;
9907         toX = currentMoveString[2] - AAA;
9908         toY = currentMoveString[3] - ONE;
9909         promoChar = currentMoveString[4];
9910         break;
9911
9912       case WhiteDrop:
9913       case BlackDrop:
9914         if (appData.debugMode)
9915           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9916         fromX = moveType == WhiteDrop ?
9917           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9918         (int) CharToPiece(ToLower(currentMoveString[0]));
9919         fromY = DROP_RANK;
9920         toX = currentMoveString[2] - AAA;
9921         toY = currentMoveString[3] - ONE;
9922         break;
9923
9924       case WhiteWins:
9925       case BlackWins:
9926       case GameIsDrawn:
9927       case GameUnfinished:
9928         if (appData.debugMode)
9929           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9930         p = strchr(yy_text, '{');
9931         if (p == NULL) p = strchr(yy_text, '(');
9932         if (p == NULL) {
9933             p = yy_text;
9934             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9935         } else {
9936             q = strchr(p, *p == '{' ? '}' : ')');
9937             if (q != NULL) *q = NULLCHAR;
9938             p++;
9939         }
9940         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9941         GameEnds(moveType, p, GE_FILE);
9942         done = TRUE;
9943         if (cmailMsgLoaded) {
9944             ClearHighlights();
9945             flipView = WhiteOnMove(currentMove);
9946             if (moveType == GameUnfinished) flipView = !flipView;
9947             if (appData.debugMode)
9948               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9949         }
9950         break;
9951
9952       case EndOfFile:
9953         if (appData.debugMode)
9954           fprintf(debugFP, "Parser hit end of file\n");
9955         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9956           case MT_NONE:
9957           case MT_CHECK:
9958             break;
9959           case MT_CHECKMATE:
9960           case MT_STAINMATE:
9961             if (WhiteOnMove(currentMove)) {
9962                 GameEnds(BlackWins, "Black mates", GE_FILE);
9963             } else {
9964                 GameEnds(WhiteWins, "White mates", GE_FILE);
9965             }
9966             break;
9967           case MT_STALEMATE:
9968             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9969             break;
9970         }
9971         done = TRUE;
9972         break;
9973
9974       case MoveNumberOne:
9975         if (lastLoadGameStart == GNUChessGame) {
9976             /* GNUChessGames have numbers, but they aren't move numbers */
9977             if (appData.debugMode)
9978               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9979                       yy_text, (int) moveType);
9980             return LoadGameOneMove(EndOfFile); /* tail recursion */
9981         }
9982         /* else fall thru */
9983
9984       case XBoardGame:
9985       case GNUChessGame:
9986       case PGNTag:
9987         /* Reached start of next game in file */
9988         if (appData.debugMode)
9989           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9990         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9991           case MT_NONE:
9992           case MT_CHECK:
9993             break;
9994           case MT_CHECKMATE:
9995           case MT_STAINMATE:
9996             if (WhiteOnMove(currentMove)) {
9997                 GameEnds(BlackWins, "Black mates", GE_FILE);
9998             } else {
9999                 GameEnds(WhiteWins, "White mates", GE_FILE);
10000             }
10001             break;
10002           case MT_STALEMATE:
10003             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10004             break;
10005         }
10006         done = TRUE;
10007         break;
10008
10009       case PositionDiagram:     /* should not happen; ignore */
10010       case ElapsedTime:         /* ignore */
10011       case NAG:                 /* ignore */
10012         if (appData.debugMode)
10013           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10014                   yy_text, (int) moveType);
10015         return LoadGameOneMove(EndOfFile); /* tail recursion */
10016
10017       case IllegalMove:
10018         if (appData.testLegality) {
10019             if (appData.debugMode)
10020               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10021             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10022                     (forwardMostMove / 2) + 1,
10023                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10024             DisplayError(move, 0);
10025             done = TRUE;
10026         } else {
10027             if (appData.debugMode)
10028               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10029                       yy_text, currentMoveString);
10030             fromX = currentMoveString[0] - AAA;
10031             fromY = currentMoveString[1] - ONE;
10032             toX = currentMoveString[2] - AAA;
10033             toY = currentMoveString[3] - ONE;
10034             promoChar = currentMoveString[4];
10035         }
10036         break;
10037
10038       case AmbiguousMove:
10039         if (appData.debugMode)
10040           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10041         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10042                 (forwardMostMove / 2) + 1,
10043                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10044         DisplayError(move, 0);
10045         done = TRUE;
10046         break;
10047
10048       default:
10049       case ImpossibleMove:
10050         if (appData.debugMode)
10051           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10052         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10053                 (forwardMostMove / 2) + 1,
10054                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10055         DisplayError(move, 0);
10056         done = TRUE;
10057         break;
10058     }
10059
10060     if (done) {
10061         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10062             DrawPosition(FALSE, boards[currentMove]);
10063             DisplayBothClocks();
10064             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10065               DisplayComment(currentMove - 1, commentList[currentMove]);
10066         }
10067         (void) StopLoadGameTimer();
10068         gameFileFP = NULL;
10069         cmailOldMove = forwardMostMove;
10070         return FALSE;
10071     } else {
10072         /* currentMoveString is set as a side-effect of yylex */
10073
10074         thinkOutput[0] = NULLCHAR;
10075         MakeMove(fromX, fromY, toX, toY, promoChar);
10076         currentMove = forwardMostMove;
10077         return TRUE;
10078     }
10079 }
10080
10081 /* Load the nth game from the given file */
10082 int
10083 LoadGameFromFile(filename, n, title, useList)
10084      char *filename;
10085      int n;
10086      char *title;
10087      /*Boolean*/ int useList;
10088 {
10089     FILE *f;
10090     char buf[MSG_SIZ];
10091
10092     if (strcmp(filename, "-") == 0) {
10093         f = stdin;
10094         title = "stdin";
10095     } else {
10096         f = fopen(filename, "rb");
10097         if (f == NULL) {
10098           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10099             DisplayError(buf, errno);
10100             return FALSE;
10101         }
10102     }
10103     if (fseek(f, 0, 0) == -1) {
10104         /* f is not seekable; probably a pipe */
10105         useList = FALSE;
10106     }
10107     if (useList && n == 0) {
10108         int error = GameListBuild(f);
10109         if (error) {
10110             DisplayError(_("Cannot build game list"), error);
10111         } else if (!ListEmpty(&gameList) &&
10112                    ((ListGame *) gameList.tailPred)->number > 1) {
10113             GameListPopUp(f, title);
10114             return TRUE;
10115         }
10116         GameListDestroy();
10117         n = 1;
10118     }
10119     if (n == 0) n = 1;
10120     return LoadGame(f, n, title, FALSE);
10121 }
10122
10123
10124 void
10125 MakeRegisteredMove()
10126 {
10127     int fromX, fromY, toX, toY;
10128     char promoChar;
10129     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10130         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10131           case CMAIL_MOVE:
10132           case CMAIL_DRAW:
10133             if (appData.debugMode)
10134               fprintf(debugFP, "Restoring %s for game %d\n",
10135                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10136
10137             thinkOutput[0] = NULLCHAR;
10138             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10139             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10140             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10141             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10142             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10143             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10144             MakeMove(fromX, fromY, toX, toY, promoChar);
10145             ShowMove(fromX, fromY, toX, toY);
10146
10147             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10148               case MT_NONE:
10149               case MT_CHECK:
10150                 break;
10151
10152               case MT_CHECKMATE:
10153               case MT_STAINMATE:
10154                 if (WhiteOnMove(currentMove)) {
10155                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10156                 } else {
10157                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10158                 }
10159                 break;
10160
10161               case MT_STALEMATE:
10162                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10163                 break;
10164             }
10165
10166             break;
10167
10168           case CMAIL_RESIGN:
10169             if (WhiteOnMove(currentMove)) {
10170                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10171             } else {
10172                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10173             }
10174             break;
10175
10176           case CMAIL_ACCEPT:
10177             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10178             break;
10179
10180           default:
10181             break;
10182         }
10183     }
10184
10185     return;
10186 }
10187
10188 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10189 int
10190 CmailLoadGame(f, gameNumber, title, useList)
10191      FILE *f;
10192      int gameNumber;
10193      char *title;
10194      int useList;
10195 {
10196     int retVal;
10197
10198     if (gameNumber > nCmailGames) {
10199         DisplayError(_("No more games in this message"), 0);
10200         return FALSE;
10201     }
10202     if (f == lastLoadGameFP) {
10203         int offset = gameNumber - lastLoadGameNumber;
10204         if (offset == 0) {
10205             cmailMsg[0] = NULLCHAR;
10206             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10207                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10208                 nCmailMovesRegistered--;
10209             }
10210             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10211             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10212                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10213             }
10214         } else {
10215             if (! RegisterMove()) return FALSE;
10216         }
10217     }
10218
10219     retVal = LoadGame(f, gameNumber, title, useList);
10220
10221     /* Make move registered during previous look at this game, if any */
10222     MakeRegisteredMove();
10223
10224     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10225         commentList[currentMove]
10226           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10227         DisplayComment(currentMove - 1, commentList[currentMove]);
10228     }
10229
10230     return retVal;
10231 }
10232
10233 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10234 int
10235 ReloadGame(offset)
10236      int offset;
10237 {
10238     int gameNumber = lastLoadGameNumber + offset;
10239     if (lastLoadGameFP == NULL) {
10240         DisplayError(_("No game has been loaded yet"), 0);
10241         return FALSE;
10242     }
10243     if (gameNumber <= 0) {
10244         DisplayError(_("Can't back up any further"), 0);
10245         return FALSE;
10246     }
10247     if (cmailMsgLoaded) {
10248         return CmailLoadGame(lastLoadGameFP, gameNumber,
10249                              lastLoadGameTitle, lastLoadGameUseList);
10250     } else {
10251         return LoadGame(lastLoadGameFP, gameNumber,
10252                         lastLoadGameTitle, lastLoadGameUseList);
10253     }
10254 }
10255
10256
10257
10258 /* Load the nth game from open file f */
10259 int
10260 LoadGame(f, gameNumber, title, useList)
10261      FILE *f;
10262      int gameNumber;
10263      char *title;
10264      int useList;
10265 {
10266     ChessMove cm;
10267     char buf[MSG_SIZ];
10268     int gn = gameNumber;
10269     ListGame *lg = NULL;
10270     int numPGNTags = 0;
10271     int err;
10272     GameMode oldGameMode;
10273     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10274
10275     if (appData.debugMode)
10276         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10277
10278     if (gameMode == Training )
10279         SetTrainingModeOff();
10280
10281     oldGameMode = gameMode;
10282     if (gameMode != BeginningOfGame) {
10283       Reset(FALSE, TRUE);
10284     }
10285
10286     gameFileFP = f;
10287     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10288         fclose(lastLoadGameFP);
10289     }
10290
10291     if (useList) {
10292         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10293
10294         if (lg) {
10295             fseek(f, lg->offset, 0);
10296             GameListHighlight(gameNumber);
10297             gn = 1;
10298         }
10299         else {
10300             DisplayError(_("Game number out of range"), 0);
10301             return FALSE;
10302         }
10303     } else {
10304         GameListDestroy();
10305         if (fseek(f, 0, 0) == -1) {
10306             if (f == lastLoadGameFP ?
10307                 gameNumber == lastLoadGameNumber + 1 :
10308                 gameNumber == 1) {
10309                 gn = 1;
10310             } else {
10311                 DisplayError(_("Can't seek on game file"), 0);
10312                 return FALSE;
10313             }
10314         }
10315     }
10316     lastLoadGameFP = f;
10317     lastLoadGameNumber = gameNumber;
10318     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10319     lastLoadGameUseList = useList;
10320
10321     yynewfile(f);
10322
10323     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10324       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10325                 lg->gameInfo.black);
10326             DisplayTitle(buf);
10327     } else if (*title != NULLCHAR) {
10328         if (gameNumber > 1) {
10329           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10330             DisplayTitle(buf);
10331         } else {
10332             DisplayTitle(title);
10333         }
10334     }
10335
10336     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10337         gameMode = PlayFromGameFile;
10338         ModeHighlight();
10339     }
10340
10341     currentMove = forwardMostMove = backwardMostMove = 0;
10342     CopyBoard(boards[0], initialPosition);
10343     StopClocks();
10344
10345     /*
10346      * Skip the first gn-1 games in the file.
10347      * Also skip over anything that precedes an identifiable
10348      * start of game marker, to avoid being confused by
10349      * garbage at the start of the file.  Currently
10350      * recognized start of game markers are the move number "1",
10351      * the pattern "gnuchess .* game", the pattern
10352      * "^[#;%] [^ ]* game file", and a PGN tag block.
10353      * A game that starts with one of the latter two patterns
10354      * will also have a move number 1, possibly
10355      * following a position diagram.
10356      * 5-4-02: Let's try being more lenient and allowing a game to
10357      * start with an unnumbered move.  Does that break anything?
10358      */
10359     cm = lastLoadGameStart = EndOfFile;
10360     while (gn > 0) {
10361         yyboardindex = forwardMostMove;
10362         cm = (ChessMove) Myylex();
10363         switch (cm) {
10364           case EndOfFile:
10365             if (cmailMsgLoaded) {
10366                 nCmailGames = CMAIL_MAX_GAMES - gn;
10367             } else {
10368                 Reset(TRUE, TRUE);
10369                 DisplayError(_("Game not found in file"), 0);
10370             }
10371             return FALSE;
10372
10373           case GNUChessGame:
10374           case XBoardGame:
10375             gn--;
10376             lastLoadGameStart = cm;
10377             break;
10378
10379           case MoveNumberOne:
10380             switch (lastLoadGameStart) {
10381               case GNUChessGame:
10382               case XBoardGame:
10383               case PGNTag:
10384                 break;
10385               case MoveNumberOne:
10386               case EndOfFile:
10387                 gn--;           /* count this game */
10388                 lastLoadGameStart = cm;
10389                 break;
10390               default:
10391                 /* impossible */
10392                 break;
10393             }
10394             break;
10395
10396           case PGNTag:
10397             switch (lastLoadGameStart) {
10398               case GNUChessGame:
10399               case PGNTag:
10400               case MoveNumberOne:
10401               case EndOfFile:
10402                 gn--;           /* count this game */
10403                 lastLoadGameStart = cm;
10404                 break;
10405               case XBoardGame:
10406                 lastLoadGameStart = cm; /* game counted already */
10407                 break;
10408               default:
10409                 /* impossible */
10410                 break;
10411             }
10412             if (gn > 0) {
10413                 do {
10414                     yyboardindex = forwardMostMove;
10415                     cm = (ChessMove) Myylex();
10416                 } while (cm == PGNTag || cm == Comment);
10417             }
10418             break;
10419
10420           case WhiteWins:
10421           case BlackWins:
10422           case GameIsDrawn:
10423             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10424                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10425                     != CMAIL_OLD_RESULT) {
10426                     nCmailResults ++ ;
10427                     cmailResult[  CMAIL_MAX_GAMES
10428                                 - gn - 1] = CMAIL_OLD_RESULT;
10429                 }
10430             }
10431             break;
10432
10433           case NormalMove:
10434             /* Only a NormalMove can be at the start of a game
10435              * without a position diagram. */
10436             if (lastLoadGameStart == EndOfFile ) {
10437               gn--;
10438               lastLoadGameStart = MoveNumberOne;
10439             }
10440             break;
10441
10442           default:
10443             break;
10444         }
10445     }
10446
10447     if (appData.debugMode)
10448       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10449
10450     if (cm == XBoardGame) {
10451         /* Skip any header junk before position diagram and/or move 1 */
10452         for (;;) {
10453             yyboardindex = forwardMostMove;
10454             cm = (ChessMove) Myylex();
10455
10456             if (cm == EndOfFile ||
10457                 cm == GNUChessGame || cm == XBoardGame) {
10458                 /* Empty game; pretend end-of-file and handle later */
10459                 cm = EndOfFile;
10460                 break;
10461             }
10462
10463             if (cm == MoveNumberOne || cm == PositionDiagram ||
10464                 cm == PGNTag || cm == Comment)
10465               break;
10466         }
10467     } else if (cm == GNUChessGame) {
10468         if (gameInfo.event != NULL) {
10469             free(gameInfo.event);
10470         }
10471         gameInfo.event = StrSave(yy_text);
10472     }
10473
10474     startedFromSetupPosition = FALSE;
10475     while (cm == PGNTag) {
10476         if (appData.debugMode)
10477           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10478         err = ParsePGNTag(yy_text, &gameInfo);
10479         if (!err) numPGNTags++;
10480
10481         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10482         if(gameInfo.variant != oldVariant) {
10483             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10484             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10485             InitPosition(TRUE);
10486             oldVariant = gameInfo.variant;
10487             if (appData.debugMode)
10488               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10489         }
10490
10491
10492         if (gameInfo.fen != NULL) {
10493           Board initial_position;
10494           startedFromSetupPosition = TRUE;
10495           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10496             Reset(TRUE, TRUE);
10497             DisplayError(_("Bad FEN position in file"), 0);
10498             return FALSE;
10499           }
10500           CopyBoard(boards[0], initial_position);
10501           if (blackPlaysFirst) {
10502             currentMove = forwardMostMove = backwardMostMove = 1;
10503             CopyBoard(boards[1], initial_position);
10504             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10505             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10506             timeRemaining[0][1] = whiteTimeRemaining;
10507             timeRemaining[1][1] = blackTimeRemaining;
10508             if (commentList[0] != NULL) {
10509               commentList[1] = commentList[0];
10510               commentList[0] = NULL;
10511             }
10512           } else {
10513             currentMove = forwardMostMove = backwardMostMove = 0;
10514           }
10515           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10516           {   int i;
10517               initialRulePlies = FENrulePlies;
10518               for( i=0; i< nrCastlingRights; i++ )
10519                   initialRights[i] = initial_position[CASTLING][i];
10520           }
10521           yyboardindex = forwardMostMove;
10522           free(gameInfo.fen);
10523           gameInfo.fen = NULL;
10524         }
10525
10526         yyboardindex = forwardMostMove;
10527         cm = (ChessMove) Myylex();
10528
10529         /* Handle comments interspersed among the tags */
10530         while (cm == Comment) {
10531             char *p;
10532             if (appData.debugMode)
10533               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10534             p = yy_text;
10535             AppendComment(currentMove, p, FALSE);
10536             yyboardindex = forwardMostMove;
10537             cm = (ChessMove) Myylex();
10538         }
10539     }
10540
10541     /* don't rely on existence of Event tag since if game was
10542      * pasted from clipboard the Event tag may not exist
10543      */
10544     if (numPGNTags > 0){
10545         char *tags;
10546         if (gameInfo.variant == VariantNormal) {
10547           VariantClass v = StringToVariant(gameInfo.event);
10548           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10549           if(v < VariantShogi) gameInfo.variant = v;
10550         }
10551         if (!matchMode) {
10552           if( appData.autoDisplayTags ) {
10553             tags = PGNTags(&gameInfo);
10554             TagsPopUp(tags, CmailMsg());
10555             free(tags);
10556           }
10557         }
10558     } else {
10559         /* Make something up, but don't display it now */
10560         SetGameInfo();
10561         TagsPopDown();
10562     }
10563
10564     if (cm == PositionDiagram) {
10565         int i, j;
10566         char *p;
10567         Board initial_position;
10568
10569         if (appData.debugMode)
10570           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10571
10572         if (!startedFromSetupPosition) {
10573             p = yy_text;
10574             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10575               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10576                 switch (*p) {
10577                   case '{':
10578                   case '[':
10579                   case '-':
10580                   case ' ':
10581                   case '\t':
10582                   case '\n':
10583                   case '\r':
10584                     break;
10585                   default:
10586                     initial_position[i][j++] = CharToPiece(*p);
10587                     break;
10588                 }
10589             while (*p == ' ' || *p == '\t' ||
10590                    *p == '\n' || *p == '\r') p++;
10591
10592             if (strncmp(p, "black", strlen("black"))==0)
10593               blackPlaysFirst = TRUE;
10594             else
10595               blackPlaysFirst = FALSE;
10596             startedFromSetupPosition = TRUE;
10597
10598             CopyBoard(boards[0], initial_position);
10599             if (blackPlaysFirst) {
10600                 currentMove = forwardMostMove = backwardMostMove = 1;
10601                 CopyBoard(boards[1], initial_position);
10602                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10603                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10604                 timeRemaining[0][1] = whiteTimeRemaining;
10605                 timeRemaining[1][1] = blackTimeRemaining;
10606                 if (commentList[0] != NULL) {
10607                     commentList[1] = commentList[0];
10608                     commentList[0] = NULL;
10609                 }
10610             } else {
10611                 currentMove = forwardMostMove = backwardMostMove = 0;
10612             }
10613         }
10614         yyboardindex = forwardMostMove;
10615         cm = (ChessMove) Myylex();
10616     }
10617
10618     if (first.pr == NoProc) {
10619         StartChessProgram(&first);
10620     }
10621     InitChessProgram(&first, FALSE);
10622     SendToProgram("force\n", &first);
10623     if (startedFromSetupPosition) {
10624         SendBoard(&first, forwardMostMove);
10625     if (appData.debugMode) {
10626         fprintf(debugFP, "Load Game\n");
10627     }
10628         DisplayBothClocks();
10629     }
10630
10631     /* [HGM] server: flag to write setup moves in broadcast file as one */
10632     loadFlag = appData.suppressLoadMoves;
10633
10634     while (cm == Comment) {
10635         char *p;
10636         if (appData.debugMode)
10637           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10638         p = yy_text;
10639         AppendComment(currentMove, p, FALSE);
10640         yyboardindex = forwardMostMove;
10641         cm = (ChessMove) Myylex();
10642     }
10643
10644     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10645         cm == WhiteWins || cm == BlackWins ||
10646         cm == GameIsDrawn || cm == GameUnfinished) {
10647         DisplayMessage("", _("No moves in game"));
10648         if (cmailMsgLoaded) {
10649             if (appData.debugMode)
10650               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10651             ClearHighlights();
10652             flipView = FALSE;
10653         }
10654         DrawPosition(FALSE, boards[currentMove]);
10655         DisplayBothClocks();
10656         gameMode = EditGame;
10657         ModeHighlight();
10658         gameFileFP = NULL;
10659         cmailOldMove = 0;
10660         return TRUE;
10661     }
10662
10663     // [HGM] PV info: routine tests if comment empty
10664     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10665         DisplayComment(currentMove - 1, commentList[currentMove]);
10666     }
10667     if (!matchMode && appData.timeDelay != 0)
10668       DrawPosition(FALSE, boards[currentMove]);
10669
10670     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10671       programStats.ok_to_send = 1;
10672     }
10673
10674     /* if the first token after the PGN tags is a move
10675      * and not move number 1, retrieve it from the parser
10676      */
10677     if (cm != MoveNumberOne)
10678         LoadGameOneMove(cm);
10679
10680     /* load the remaining moves from the file */
10681     while (LoadGameOneMove(EndOfFile)) {
10682       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10683       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10684     }
10685
10686     /* rewind to the start of the game */
10687     currentMove = backwardMostMove;
10688
10689     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10690
10691     if (oldGameMode == AnalyzeFile ||
10692         oldGameMode == AnalyzeMode) {
10693       AnalyzeFileEvent();
10694     }
10695
10696     if (matchMode || appData.timeDelay == 0) {
10697       ToEndEvent();
10698       gameMode = EditGame;
10699       ModeHighlight();
10700     } else if (appData.timeDelay > 0) {
10701       AutoPlayGameLoop();
10702     }
10703
10704     if (appData.debugMode)
10705         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10706
10707     loadFlag = 0; /* [HGM] true game starts */
10708     return TRUE;
10709 }
10710
10711 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10712 int
10713 ReloadPosition(offset)
10714      int offset;
10715 {
10716     int positionNumber = lastLoadPositionNumber + offset;
10717     if (lastLoadPositionFP == NULL) {
10718         DisplayError(_("No position has been loaded yet"), 0);
10719         return FALSE;
10720     }
10721     if (positionNumber <= 0) {
10722         DisplayError(_("Can't back up any further"), 0);
10723         return FALSE;
10724     }
10725     return LoadPosition(lastLoadPositionFP, positionNumber,
10726                         lastLoadPositionTitle);
10727 }
10728
10729 /* Load the nth position from the given file */
10730 int
10731 LoadPositionFromFile(filename, n, title)
10732      char *filename;
10733      int n;
10734      char *title;
10735 {
10736     FILE *f;
10737     char buf[MSG_SIZ];
10738
10739     if (strcmp(filename, "-") == 0) {
10740         return LoadPosition(stdin, n, "stdin");
10741     } else {
10742         f = fopen(filename, "rb");
10743         if (f == NULL) {
10744             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10745             DisplayError(buf, errno);
10746             return FALSE;
10747         } else {
10748             return LoadPosition(f, n, title);
10749         }
10750     }
10751 }
10752
10753 /* Load the nth position from the given open file, and close it */
10754 int
10755 LoadPosition(f, positionNumber, title)
10756      FILE *f;
10757      int positionNumber;
10758      char *title;
10759 {
10760     char *p, line[MSG_SIZ];
10761     Board initial_position;
10762     int i, j, fenMode, pn;
10763
10764     if (gameMode == Training )
10765         SetTrainingModeOff();
10766
10767     if (gameMode != BeginningOfGame) {
10768         Reset(FALSE, TRUE);
10769     }
10770     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10771         fclose(lastLoadPositionFP);
10772     }
10773     if (positionNumber == 0) positionNumber = 1;
10774     lastLoadPositionFP = f;
10775     lastLoadPositionNumber = positionNumber;
10776     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10777     if (first.pr == NoProc) {
10778       StartChessProgram(&first);
10779       InitChessProgram(&first, FALSE);
10780     }
10781     pn = positionNumber;
10782     if (positionNumber < 0) {
10783         /* Negative position number means to seek to that byte offset */
10784         if (fseek(f, -positionNumber, 0) == -1) {
10785             DisplayError(_("Can't seek on position file"), 0);
10786             return FALSE;
10787         };
10788         pn = 1;
10789     } else {
10790         if (fseek(f, 0, 0) == -1) {
10791             if (f == lastLoadPositionFP ?
10792                 positionNumber == lastLoadPositionNumber + 1 :
10793                 positionNumber == 1) {
10794                 pn = 1;
10795             } else {
10796                 DisplayError(_("Can't seek on position file"), 0);
10797                 return FALSE;
10798             }
10799         }
10800     }
10801     /* See if this file is FEN or old-style xboard */
10802     if (fgets(line, MSG_SIZ, f) == NULL) {
10803         DisplayError(_("Position not found in file"), 0);
10804         return FALSE;
10805     }
10806     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10807     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10808
10809     if (pn >= 2) {
10810         if (fenMode || line[0] == '#') pn--;
10811         while (pn > 0) {
10812             /* skip positions before number pn */
10813             if (fgets(line, MSG_SIZ, f) == NULL) {
10814                 Reset(TRUE, TRUE);
10815                 DisplayError(_("Position not found in file"), 0);
10816                 return FALSE;
10817             }
10818             if (fenMode || line[0] == '#') pn--;
10819         }
10820     }
10821
10822     if (fenMode) {
10823         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10824             DisplayError(_("Bad FEN position in file"), 0);
10825             return FALSE;
10826         }
10827     } else {
10828         (void) fgets(line, MSG_SIZ, f);
10829         (void) fgets(line, MSG_SIZ, f);
10830
10831         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10832             (void) fgets(line, MSG_SIZ, f);
10833             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10834                 if (*p == ' ')
10835                   continue;
10836                 initial_position[i][j++] = CharToPiece(*p);
10837             }
10838         }
10839
10840         blackPlaysFirst = FALSE;
10841         if (!feof(f)) {
10842             (void) fgets(line, MSG_SIZ, f);
10843             if (strncmp(line, "black", strlen("black"))==0)
10844               blackPlaysFirst = TRUE;
10845         }
10846     }
10847     startedFromSetupPosition = TRUE;
10848
10849     SendToProgram("force\n", &first);
10850     CopyBoard(boards[0], initial_position);
10851     if (blackPlaysFirst) {
10852         currentMove = forwardMostMove = backwardMostMove = 1;
10853         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10854         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10855         CopyBoard(boards[1], initial_position);
10856         DisplayMessage("", _("Black to play"));
10857     } else {
10858         currentMove = forwardMostMove = backwardMostMove = 0;
10859         DisplayMessage("", _("White to play"));
10860     }
10861     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10862     SendBoard(&first, forwardMostMove);
10863     if (appData.debugMode) {
10864 int i, j;
10865   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10866   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10867         fprintf(debugFP, "Load Position\n");
10868     }
10869
10870     if (positionNumber > 1) {
10871       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10872         DisplayTitle(line);
10873     } else {
10874         DisplayTitle(title);
10875     }
10876     gameMode = EditGame;
10877     ModeHighlight();
10878     ResetClocks();
10879     timeRemaining[0][1] = whiteTimeRemaining;
10880     timeRemaining[1][1] = blackTimeRemaining;
10881     DrawPosition(FALSE, boards[currentMove]);
10882
10883     return TRUE;
10884 }
10885
10886
10887 void
10888 CopyPlayerNameIntoFileName(dest, src)
10889      char **dest, *src;
10890 {
10891     while (*src != NULLCHAR && *src != ',') {
10892         if (*src == ' ') {
10893             *(*dest)++ = '_';
10894             src++;
10895         } else {
10896             *(*dest)++ = *src++;
10897         }
10898     }
10899 }
10900
10901 char *DefaultFileName(ext)
10902      char *ext;
10903 {
10904     static char def[MSG_SIZ];
10905     char *p;
10906
10907     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10908         p = def;
10909         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10910         *p++ = '-';
10911         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10912         *p++ = '.';
10913         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10914     } else {
10915         def[0] = NULLCHAR;
10916     }
10917     return def;
10918 }
10919
10920 /* Save the current game to the given file */
10921 int
10922 SaveGameToFile(filename, append)
10923      char *filename;
10924      int append;
10925 {
10926     FILE *f;
10927     char buf[MSG_SIZ];
10928
10929     if (strcmp(filename, "-") == 0) {
10930         return SaveGame(stdout, 0, NULL);
10931     } else {
10932         f = fopen(filename, append ? "a" : "w");
10933         if (f == NULL) {
10934             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10935             DisplayError(buf, errno);
10936             return FALSE;
10937         } else {
10938             return SaveGame(f, 0, NULL);
10939         }
10940     }
10941 }
10942
10943 char *
10944 SavePart(str)
10945      char *str;
10946 {
10947     static char buf[MSG_SIZ];
10948     char *p;
10949
10950     p = strchr(str, ' ');
10951     if (p == NULL) return str;
10952     strncpy(buf, str, p - str);
10953     buf[p - str] = NULLCHAR;
10954     return buf;
10955 }
10956
10957 #define PGN_MAX_LINE 75
10958
10959 #define PGN_SIDE_WHITE  0
10960 #define PGN_SIDE_BLACK  1
10961
10962 /* [AS] */
10963 static int FindFirstMoveOutOfBook( int side )
10964 {
10965     int result = -1;
10966
10967     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10968         int index = backwardMostMove;
10969         int has_book_hit = 0;
10970
10971         if( (index % 2) != side ) {
10972             index++;
10973         }
10974
10975         while( index < forwardMostMove ) {
10976             /* Check to see if engine is in book */
10977             int depth = pvInfoList[index].depth;
10978             int score = pvInfoList[index].score;
10979             int in_book = 0;
10980
10981             if( depth <= 2 ) {
10982                 in_book = 1;
10983             }
10984             else if( score == 0 && depth == 63 ) {
10985                 in_book = 1; /* Zappa */
10986             }
10987             else if( score == 2 && depth == 99 ) {
10988                 in_book = 1; /* Abrok */
10989             }
10990
10991             has_book_hit += in_book;
10992
10993             if( ! in_book ) {
10994                 result = index;
10995
10996                 break;
10997             }
10998
10999             index += 2;
11000         }
11001     }
11002
11003     return result;
11004 }
11005
11006 /* [AS] */
11007 void GetOutOfBookInfo( char * buf )
11008 {
11009     int oob[2];
11010     int i;
11011     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11012
11013     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11014     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11015
11016     *buf = '\0';
11017
11018     if( oob[0] >= 0 || oob[1] >= 0 ) {
11019         for( i=0; i<2; i++ ) {
11020             int idx = oob[i];
11021
11022             if( idx >= 0 ) {
11023                 if( i > 0 && oob[0] >= 0 ) {
11024                     strcat( buf, "   " );
11025                 }
11026
11027                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11028                 sprintf( buf+strlen(buf), "%s%.2f",
11029                     pvInfoList[idx].score >= 0 ? "+" : "",
11030                     pvInfoList[idx].score / 100.0 );
11031             }
11032         }
11033     }
11034 }
11035
11036 /* Save game in PGN style and close the file */
11037 int
11038 SaveGamePGN(f)
11039      FILE *f;
11040 {
11041     int i, offset, linelen, newblock;
11042     time_t tm;
11043 //    char *movetext;
11044     char numtext[32];
11045     int movelen, numlen, blank;
11046     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11047
11048     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11049
11050     tm = time((time_t *) NULL);
11051
11052     PrintPGNTags(f, &gameInfo);
11053
11054     if (backwardMostMove > 0 || startedFromSetupPosition) {
11055         char *fen = PositionToFEN(backwardMostMove, NULL);
11056         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11057         fprintf(f, "\n{--------------\n");
11058         PrintPosition(f, backwardMostMove);
11059         fprintf(f, "--------------}\n");
11060         free(fen);
11061     }
11062     else {
11063         /* [AS] Out of book annotation */
11064         if( appData.saveOutOfBookInfo ) {
11065             char buf[64];
11066
11067             GetOutOfBookInfo( buf );
11068
11069             if( buf[0] != '\0' ) {
11070                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11071             }
11072         }
11073
11074         fprintf(f, "\n");
11075     }
11076
11077     i = backwardMostMove;
11078     linelen = 0;
11079     newblock = TRUE;
11080
11081     while (i < forwardMostMove) {
11082         /* Print comments preceding this move */
11083         if (commentList[i] != NULL) {
11084             if (linelen > 0) fprintf(f, "\n");
11085             fprintf(f, "%s", commentList[i]);
11086             linelen = 0;
11087             newblock = TRUE;
11088         }
11089
11090         /* Format move number */
11091         if ((i % 2) == 0)
11092           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11093         else
11094           if (newblock)
11095             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11096           else
11097             numtext[0] = NULLCHAR;
11098
11099         numlen = strlen(numtext);
11100         newblock = FALSE;
11101
11102         /* Print move number */
11103         blank = linelen > 0 && numlen > 0;
11104         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11105             fprintf(f, "\n");
11106             linelen = 0;
11107             blank = 0;
11108         }
11109         if (blank) {
11110             fprintf(f, " ");
11111             linelen++;
11112         }
11113         fprintf(f, "%s", numtext);
11114         linelen += numlen;
11115
11116         /* Get move */
11117         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11118         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11119
11120         /* Print move */
11121         blank = linelen > 0 && movelen > 0;
11122         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11123             fprintf(f, "\n");
11124             linelen = 0;
11125             blank = 0;
11126         }
11127         if (blank) {
11128             fprintf(f, " ");
11129             linelen++;
11130         }
11131         fprintf(f, "%s", move_buffer);
11132         linelen += movelen;
11133
11134         /* [AS] Add PV info if present */
11135         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11136             /* [HGM] add time */
11137             char buf[MSG_SIZ]; int seconds;
11138
11139             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11140
11141             if( seconds <= 0)
11142               buf[0] = 0;
11143             else
11144               if( seconds < 30 )
11145                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11146               else
11147                 {
11148                   seconds = (seconds + 4)/10; // round to full seconds
11149                   if( seconds < 60 )
11150                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11151                   else
11152                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11153                 }
11154
11155             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11156                       pvInfoList[i].score >= 0 ? "+" : "",
11157                       pvInfoList[i].score / 100.0,
11158                       pvInfoList[i].depth,
11159                       buf );
11160
11161             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11162
11163             /* Print score/depth */
11164             blank = linelen > 0 && movelen > 0;
11165             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11166                 fprintf(f, "\n");
11167                 linelen = 0;
11168                 blank = 0;
11169             }
11170             if (blank) {
11171                 fprintf(f, " ");
11172                 linelen++;
11173             }
11174             fprintf(f, "%s", move_buffer);
11175             linelen += movelen;
11176         }
11177
11178         i++;
11179     }
11180
11181     /* Start a new line */
11182     if (linelen > 0) fprintf(f, "\n");
11183
11184     /* Print comments after last move */
11185     if (commentList[i] != NULL) {
11186         fprintf(f, "%s\n", commentList[i]);
11187     }
11188
11189     /* Print result */
11190     if (gameInfo.resultDetails != NULL &&
11191         gameInfo.resultDetails[0] != NULLCHAR) {
11192         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11193                 PGNResult(gameInfo.result));
11194     } else {
11195         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11196     }
11197
11198     fclose(f);
11199     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11200     return TRUE;
11201 }
11202
11203 /* Save game in old style and close the file */
11204 int
11205 SaveGameOldStyle(f)
11206      FILE *f;
11207 {
11208     int i, offset;
11209     time_t tm;
11210
11211     tm = time((time_t *) NULL);
11212
11213     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11214     PrintOpponents(f);
11215
11216     if (backwardMostMove > 0 || startedFromSetupPosition) {
11217         fprintf(f, "\n[--------------\n");
11218         PrintPosition(f, backwardMostMove);
11219         fprintf(f, "--------------]\n");
11220     } else {
11221         fprintf(f, "\n");
11222     }
11223
11224     i = backwardMostMove;
11225     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11226
11227     while (i < forwardMostMove) {
11228         if (commentList[i] != NULL) {
11229             fprintf(f, "[%s]\n", commentList[i]);
11230         }
11231
11232         if ((i % 2) == 1) {
11233             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11234             i++;
11235         } else {
11236             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11237             i++;
11238             if (commentList[i] != NULL) {
11239                 fprintf(f, "\n");
11240                 continue;
11241             }
11242             if (i >= forwardMostMove) {
11243                 fprintf(f, "\n");
11244                 break;
11245             }
11246             fprintf(f, "%s\n", parseList[i]);
11247             i++;
11248         }
11249     }
11250
11251     if (commentList[i] != NULL) {
11252         fprintf(f, "[%s]\n", commentList[i]);
11253     }
11254
11255     /* This isn't really the old style, but it's close enough */
11256     if (gameInfo.resultDetails != NULL &&
11257         gameInfo.resultDetails[0] != NULLCHAR) {
11258         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11259                 gameInfo.resultDetails);
11260     } else {
11261         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11262     }
11263
11264     fclose(f);
11265     return TRUE;
11266 }
11267
11268 /* Save the current game to open file f and close the file */
11269 int
11270 SaveGame(f, dummy, dummy2)
11271      FILE *f;
11272      int dummy;
11273      char *dummy2;
11274 {
11275     if (gameMode == EditPosition) EditPositionDone(TRUE);
11276     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11277     if (appData.oldSaveStyle)
11278       return SaveGameOldStyle(f);
11279     else
11280       return SaveGamePGN(f);
11281 }
11282
11283 /* Save the current position to the given file */
11284 int
11285 SavePositionToFile(filename)
11286      char *filename;
11287 {
11288     FILE *f;
11289     char buf[MSG_SIZ];
11290
11291     if (strcmp(filename, "-") == 0) {
11292         return SavePosition(stdout, 0, NULL);
11293     } else {
11294         f = fopen(filename, "a");
11295         if (f == NULL) {
11296             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11297             DisplayError(buf, errno);
11298             return FALSE;
11299         } else {
11300             SavePosition(f, 0, NULL);
11301             return TRUE;
11302         }
11303     }
11304 }
11305
11306 /* Save the current position to the given open file and close the file */
11307 int
11308 SavePosition(f, dummy, dummy2)
11309      FILE *f;
11310      int dummy;
11311      char *dummy2;
11312 {
11313     time_t tm;
11314     char *fen;
11315
11316     if (gameMode == EditPosition) EditPositionDone(TRUE);
11317     if (appData.oldSaveStyle) {
11318         tm = time((time_t *) NULL);
11319
11320         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11321         PrintOpponents(f);
11322         fprintf(f, "[--------------\n");
11323         PrintPosition(f, currentMove);
11324         fprintf(f, "--------------]\n");
11325     } else {
11326         fen = PositionToFEN(currentMove, NULL);
11327         fprintf(f, "%s\n", fen);
11328         free(fen);
11329     }
11330     fclose(f);
11331     return TRUE;
11332 }
11333
11334 void
11335 ReloadCmailMsgEvent(unregister)
11336      int unregister;
11337 {
11338 #if !WIN32
11339     static char *inFilename = NULL;
11340     static char *outFilename;
11341     int i;
11342     struct stat inbuf, outbuf;
11343     int status;
11344
11345     /* Any registered moves are unregistered if unregister is set, */
11346     /* i.e. invoked by the signal handler */
11347     if (unregister) {
11348         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11349             cmailMoveRegistered[i] = FALSE;
11350             if (cmailCommentList[i] != NULL) {
11351                 free(cmailCommentList[i]);
11352                 cmailCommentList[i] = NULL;
11353             }
11354         }
11355         nCmailMovesRegistered = 0;
11356     }
11357
11358     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11359         cmailResult[i] = CMAIL_NOT_RESULT;
11360     }
11361     nCmailResults = 0;
11362
11363     if (inFilename == NULL) {
11364         /* Because the filenames are static they only get malloced once  */
11365         /* and they never get freed                                      */
11366         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11367         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11368
11369         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11370         sprintf(outFilename, "%s.out", appData.cmailGameName);
11371     }
11372
11373     status = stat(outFilename, &outbuf);
11374     if (status < 0) {
11375         cmailMailedMove = FALSE;
11376     } else {
11377         status = stat(inFilename, &inbuf);
11378         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11379     }
11380
11381     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11382        counts the games, notes how each one terminated, etc.
11383
11384        It would be nice to remove this kludge and instead gather all
11385        the information while building the game list.  (And to keep it
11386        in the game list nodes instead of having a bunch of fixed-size
11387        parallel arrays.)  Note this will require getting each game's
11388        termination from the PGN tags, as the game list builder does
11389        not process the game moves.  --mann
11390        */
11391     cmailMsgLoaded = TRUE;
11392     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11393
11394     /* Load first game in the file or popup game menu */
11395     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11396
11397 #endif /* !WIN32 */
11398     return;
11399 }
11400
11401 int
11402 RegisterMove()
11403 {
11404     FILE *f;
11405     char string[MSG_SIZ];
11406
11407     if (   cmailMailedMove
11408         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11409         return TRUE;            /* Allow free viewing  */
11410     }
11411
11412     /* Unregister move to ensure that we don't leave RegisterMove        */
11413     /* with the move registered when the conditions for registering no   */
11414     /* longer hold                                                       */
11415     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11416         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11417         nCmailMovesRegistered --;
11418
11419         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11420           {
11421               free(cmailCommentList[lastLoadGameNumber - 1]);
11422               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11423           }
11424     }
11425
11426     if (cmailOldMove == -1) {
11427         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11428         return FALSE;
11429     }
11430
11431     if (currentMove > cmailOldMove + 1) {
11432         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11433         return FALSE;
11434     }
11435
11436     if (currentMove < cmailOldMove) {
11437         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11438         return FALSE;
11439     }
11440
11441     if (forwardMostMove > currentMove) {
11442         /* Silently truncate extra moves */
11443         TruncateGame();
11444     }
11445
11446     if (   (currentMove == cmailOldMove + 1)
11447         || (   (currentMove == cmailOldMove)
11448             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11449                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11450         if (gameInfo.result != GameUnfinished) {
11451             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11452         }
11453
11454         if (commentList[currentMove] != NULL) {
11455             cmailCommentList[lastLoadGameNumber - 1]
11456               = StrSave(commentList[currentMove]);
11457         }
11458         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11459
11460         if (appData.debugMode)
11461           fprintf(debugFP, "Saving %s for game %d\n",
11462                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11463
11464         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11465
11466         f = fopen(string, "w");
11467         if (appData.oldSaveStyle) {
11468             SaveGameOldStyle(f); /* also closes the file */
11469
11470             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11471             f = fopen(string, "w");
11472             SavePosition(f, 0, NULL); /* also closes the file */
11473         } else {
11474             fprintf(f, "{--------------\n");
11475             PrintPosition(f, currentMove);
11476             fprintf(f, "--------------}\n\n");
11477
11478             SaveGame(f, 0, NULL); /* also closes the file*/
11479         }
11480
11481         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11482         nCmailMovesRegistered ++;
11483     } else if (nCmailGames == 1) {
11484         DisplayError(_("You have not made a move yet"), 0);
11485         return FALSE;
11486     }
11487
11488     return TRUE;
11489 }
11490
11491 void
11492 MailMoveEvent()
11493 {
11494 #if !WIN32
11495     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11496     FILE *commandOutput;
11497     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11498     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11499     int nBuffers;
11500     int i;
11501     int archived;
11502     char *arcDir;
11503
11504     if (! cmailMsgLoaded) {
11505         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11506         return;
11507     }
11508
11509     if (nCmailGames == nCmailResults) {
11510         DisplayError(_("No unfinished games"), 0);
11511         return;
11512     }
11513
11514 #if CMAIL_PROHIBIT_REMAIL
11515     if (cmailMailedMove) {
11516       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);
11517         DisplayError(msg, 0);
11518         return;
11519     }
11520 #endif
11521
11522     if (! (cmailMailedMove || RegisterMove())) return;
11523
11524     if (   cmailMailedMove
11525         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11526       snprintf(string, MSG_SIZ, partCommandString,
11527                appData.debugMode ? " -v" : "", appData.cmailGameName);
11528         commandOutput = popen(string, "r");
11529
11530         if (commandOutput == NULL) {
11531             DisplayError(_("Failed to invoke cmail"), 0);
11532         } else {
11533             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11534                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11535             }
11536             if (nBuffers > 1) {
11537                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11538                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11539                 nBytes = MSG_SIZ - 1;
11540             } else {
11541                 (void) memcpy(msg, buffer, nBytes);
11542             }
11543             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11544
11545             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11546                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11547
11548                 archived = TRUE;
11549                 for (i = 0; i < nCmailGames; i ++) {
11550                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11551                         archived = FALSE;
11552                     }
11553                 }
11554                 if (   archived
11555                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11556                         != NULL)) {
11557                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11558                            arcDir,
11559                            appData.cmailGameName,
11560                            gameInfo.date);
11561                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11562                     cmailMsgLoaded = FALSE;
11563                 }
11564             }
11565
11566             DisplayInformation(msg);
11567             pclose(commandOutput);
11568         }
11569     } else {
11570         if ((*cmailMsg) != '\0') {
11571             DisplayInformation(cmailMsg);
11572         }
11573     }
11574
11575     return;
11576 #endif /* !WIN32 */
11577 }
11578
11579 char *
11580 CmailMsg()
11581 {
11582 #if WIN32
11583     return NULL;
11584 #else
11585     int  prependComma = 0;
11586     char number[5];
11587     char string[MSG_SIZ];       /* Space for game-list */
11588     int  i;
11589
11590     if (!cmailMsgLoaded) return "";
11591
11592     if (cmailMailedMove) {
11593       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11594     } else {
11595         /* Create a list of games left */
11596       snprintf(string, MSG_SIZ, "[");
11597         for (i = 0; i < nCmailGames; i ++) {
11598             if (! (   cmailMoveRegistered[i]
11599                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11600                 if (prependComma) {
11601                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11602                 } else {
11603                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11604                     prependComma = 1;
11605                 }
11606
11607                 strcat(string, number);
11608             }
11609         }
11610         strcat(string, "]");
11611
11612         if (nCmailMovesRegistered + nCmailResults == 0) {
11613             switch (nCmailGames) {
11614               case 1:
11615                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11616                 break;
11617
11618               case 2:
11619                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11620                 break;
11621
11622               default:
11623                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11624                          nCmailGames);
11625                 break;
11626             }
11627         } else {
11628             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11629               case 1:
11630                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11631                          string);
11632                 break;
11633
11634               case 0:
11635                 if (nCmailResults == nCmailGames) {
11636                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11637                 } else {
11638                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11639                 }
11640                 break;
11641
11642               default:
11643                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11644                          string);
11645             }
11646         }
11647     }
11648     return cmailMsg;
11649 #endif /* WIN32 */
11650 }
11651
11652 void
11653 ResetGameEvent()
11654 {
11655     if (gameMode == Training)
11656       SetTrainingModeOff();
11657
11658     Reset(TRUE, TRUE);
11659     cmailMsgLoaded = FALSE;
11660     if (appData.icsActive) {
11661       SendToICS(ics_prefix);
11662       SendToICS("refresh\n");
11663     }
11664 }
11665
11666 void
11667 ExitEvent(status)
11668      int status;
11669 {
11670     exiting++;
11671     if (exiting > 2) {
11672       /* Give up on clean exit */
11673       exit(status);
11674     }
11675     if (exiting > 1) {
11676       /* Keep trying for clean exit */
11677       return;
11678     }
11679
11680     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11681
11682     if (telnetISR != NULL) {
11683       RemoveInputSource(telnetISR);
11684     }
11685     if (icsPR != NoProc) {
11686       DestroyChildProcess(icsPR, TRUE);
11687     }
11688
11689     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11690     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11691
11692     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11693     /* make sure this other one finishes before killing it!                  */
11694     if(endingGame) { int count = 0;
11695         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11696         while(endingGame && count++ < 10) DoSleep(1);
11697         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11698     }
11699
11700     /* Kill off chess programs */
11701     if (first.pr != NoProc) {
11702         ExitAnalyzeMode();
11703
11704         DoSleep( appData.delayBeforeQuit );
11705         SendToProgram("quit\n", &first);
11706         DoSleep( appData.delayAfterQuit );
11707         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11708     }
11709     if (second.pr != NoProc) {
11710         DoSleep( appData.delayBeforeQuit );
11711         SendToProgram("quit\n", &second);
11712         DoSleep( appData.delayAfterQuit );
11713         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11714     }
11715     if (first.isr != NULL) {
11716         RemoveInputSource(first.isr);
11717     }
11718     if (second.isr != NULL) {
11719         RemoveInputSource(second.isr);
11720     }
11721
11722     ShutDownFrontEnd();
11723     exit(status);
11724 }
11725
11726 void
11727 PauseEvent()
11728 {
11729     if (appData.debugMode)
11730         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11731     if (pausing) {
11732         pausing = FALSE;
11733         ModeHighlight();
11734         if (gameMode == MachinePlaysWhite ||
11735             gameMode == MachinePlaysBlack) {
11736             StartClocks();
11737         } else {
11738             DisplayBothClocks();
11739         }
11740         if (gameMode == PlayFromGameFile) {
11741             if (appData.timeDelay >= 0)
11742                 AutoPlayGameLoop();
11743         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11744             Reset(FALSE, TRUE);
11745             SendToICS(ics_prefix);
11746             SendToICS("refresh\n");
11747         } else if (currentMove < forwardMostMove) {
11748             ForwardInner(forwardMostMove);
11749         }
11750         pauseExamInvalid = FALSE;
11751     } else {
11752         switch (gameMode) {
11753           default:
11754             return;
11755           case IcsExamining:
11756             pauseExamForwardMostMove = forwardMostMove;
11757             pauseExamInvalid = FALSE;
11758             /* fall through */
11759           case IcsObserving:
11760           case IcsPlayingWhite:
11761           case IcsPlayingBlack:
11762             pausing = TRUE;
11763             ModeHighlight();
11764             return;
11765           case PlayFromGameFile:
11766             (void) StopLoadGameTimer();
11767             pausing = TRUE;
11768             ModeHighlight();
11769             break;
11770           case BeginningOfGame:
11771             if (appData.icsActive) return;
11772             /* else fall through */
11773           case MachinePlaysWhite:
11774           case MachinePlaysBlack:
11775           case TwoMachinesPlay:
11776             if (forwardMostMove == 0)
11777               return;           /* don't pause if no one has moved */
11778             if ((gameMode == MachinePlaysWhite &&
11779                  !WhiteOnMove(forwardMostMove)) ||
11780                 (gameMode == MachinePlaysBlack &&
11781                  WhiteOnMove(forwardMostMove))) {
11782                 StopClocks();
11783             }
11784             pausing = TRUE;
11785             ModeHighlight();
11786             break;
11787         }
11788     }
11789 }
11790
11791 void
11792 EditCommentEvent()
11793 {
11794     char title[MSG_SIZ];
11795
11796     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11797       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11798     } else {
11799       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11800                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11801                parseList[currentMove - 1]);
11802     }
11803
11804     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11805 }
11806
11807
11808 void
11809 EditTagsEvent()
11810 {
11811     char *tags = PGNTags(&gameInfo);
11812     EditTagsPopUp(tags, NULL);
11813     free(tags);
11814 }
11815
11816 void
11817 AnalyzeModeEvent()
11818 {
11819     if (appData.noChessProgram || gameMode == AnalyzeMode)
11820       return;
11821
11822     if (gameMode != AnalyzeFile) {
11823         if (!appData.icsEngineAnalyze) {
11824                EditGameEvent();
11825                if (gameMode != EditGame) return;
11826         }
11827         ResurrectChessProgram();
11828         SendToProgram("analyze\n", &first);
11829         first.analyzing = TRUE;
11830         /*first.maybeThinking = TRUE;*/
11831         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11832         EngineOutputPopUp();
11833     }
11834     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11835     pausing = FALSE;
11836     ModeHighlight();
11837     SetGameInfo();
11838
11839     StartAnalysisClock();
11840     GetTimeMark(&lastNodeCountTime);
11841     lastNodeCount = 0;
11842 }
11843
11844 void
11845 AnalyzeFileEvent()
11846 {
11847     if (appData.noChessProgram || gameMode == AnalyzeFile)
11848       return;
11849
11850     if (gameMode != AnalyzeMode) {
11851         EditGameEvent();
11852         if (gameMode != EditGame) return;
11853         ResurrectChessProgram();
11854         SendToProgram("analyze\n", &first);
11855         first.analyzing = TRUE;
11856         /*first.maybeThinking = TRUE;*/
11857         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11858         EngineOutputPopUp();
11859     }
11860     gameMode = AnalyzeFile;
11861     pausing = FALSE;
11862     ModeHighlight();
11863     SetGameInfo();
11864
11865     StartAnalysisClock();
11866     GetTimeMark(&lastNodeCountTime);
11867     lastNodeCount = 0;
11868 }
11869
11870 void
11871 MachineWhiteEvent()
11872 {
11873     char buf[MSG_SIZ];
11874     char *bookHit = NULL;
11875
11876     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11877       return;
11878
11879
11880     if (gameMode == PlayFromGameFile ||
11881         gameMode == TwoMachinesPlay  ||
11882         gameMode == Training         ||
11883         gameMode == AnalyzeMode      ||
11884         gameMode == EndOfGame)
11885         EditGameEvent();
11886
11887     if (gameMode == EditPosition)
11888         EditPositionDone(TRUE);
11889
11890     if (!WhiteOnMove(currentMove)) {
11891         DisplayError(_("It is not White's turn"), 0);
11892         return;
11893     }
11894
11895     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11896       ExitAnalyzeMode();
11897
11898     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11899         gameMode == AnalyzeFile)
11900         TruncateGame();
11901
11902     ResurrectChessProgram();    /* in case it isn't running */
11903     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11904         gameMode = MachinePlaysWhite;
11905         ResetClocks();
11906     } else
11907     gameMode = MachinePlaysWhite;
11908     pausing = FALSE;
11909     ModeHighlight();
11910     SetGameInfo();
11911     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11912     DisplayTitle(buf);
11913     if (first.sendName) {
11914       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11915       SendToProgram(buf, &first);
11916     }
11917     if (first.sendTime) {
11918       if (first.useColors) {
11919         SendToProgram("black\n", &first); /*gnu kludge*/
11920       }
11921       SendTimeRemaining(&first, TRUE);
11922     }
11923     if (first.useColors) {
11924       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11925     }
11926     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11927     SetMachineThinkingEnables();
11928     first.maybeThinking = TRUE;
11929     StartClocks();
11930     firstMove = FALSE;
11931
11932     if (appData.autoFlipView && !flipView) {
11933       flipView = !flipView;
11934       DrawPosition(FALSE, NULL);
11935       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11936     }
11937
11938     if(bookHit) { // [HGM] book: simulate book reply
11939         static char bookMove[MSG_SIZ]; // a bit generous?
11940
11941         programStats.nodes = programStats.depth = programStats.time =
11942         programStats.score = programStats.got_only_move = 0;
11943         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11944
11945         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11946         strcat(bookMove, bookHit);
11947         HandleMachineMove(bookMove, &first);
11948     }
11949 }
11950
11951 void
11952 MachineBlackEvent()
11953 {
11954   char buf[MSG_SIZ];
11955   char *bookHit = NULL;
11956
11957     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11958         return;
11959
11960
11961     if (gameMode == PlayFromGameFile ||
11962         gameMode == TwoMachinesPlay  ||
11963         gameMode == Training         ||
11964         gameMode == AnalyzeMode      ||
11965         gameMode == EndOfGame)
11966         EditGameEvent();
11967
11968     if (gameMode == EditPosition)
11969         EditPositionDone(TRUE);
11970
11971     if (WhiteOnMove(currentMove)) {
11972         DisplayError(_("It is not Black's turn"), 0);
11973         return;
11974     }
11975
11976     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11977       ExitAnalyzeMode();
11978
11979     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11980         gameMode == AnalyzeFile)
11981         TruncateGame();
11982
11983     ResurrectChessProgram();    /* in case it isn't running */
11984     gameMode = MachinePlaysBlack;
11985     pausing = FALSE;
11986     ModeHighlight();
11987     SetGameInfo();
11988     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11989     DisplayTitle(buf);
11990     if (first.sendName) {
11991       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11992       SendToProgram(buf, &first);
11993     }
11994     if (first.sendTime) {
11995       if (first.useColors) {
11996         SendToProgram("white\n", &first); /*gnu kludge*/
11997       }
11998       SendTimeRemaining(&first, FALSE);
11999     }
12000     if (first.useColors) {
12001       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12002     }
12003     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12004     SetMachineThinkingEnables();
12005     first.maybeThinking = TRUE;
12006     StartClocks();
12007
12008     if (appData.autoFlipView && flipView) {
12009       flipView = !flipView;
12010       DrawPosition(FALSE, NULL);
12011       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12012     }
12013     if(bookHit) { // [HGM] book: simulate book reply
12014         static char bookMove[MSG_SIZ]; // a bit generous?
12015
12016         programStats.nodes = programStats.depth = programStats.time =
12017         programStats.score = programStats.got_only_move = 0;
12018         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12019
12020         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12021         strcat(bookMove, bookHit);
12022         HandleMachineMove(bookMove, &first);
12023     }
12024 }
12025
12026
12027 void
12028 DisplayTwoMachinesTitle()
12029 {
12030     char buf[MSG_SIZ];
12031     if (appData.matchGames > 0) {
12032         if (first.twoMachinesColor[0] == 'w') {
12033           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12034                    gameInfo.white, gameInfo.black,
12035                    first.matchWins, second.matchWins,
12036                    matchGame - 1 - (first.matchWins + second.matchWins));
12037         } else {
12038           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12039                    gameInfo.white, gameInfo.black,
12040                    second.matchWins, first.matchWins,
12041                    matchGame - 1 - (first.matchWins + second.matchWins));
12042         }
12043     } else {
12044       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12045     }
12046     DisplayTitle(buf);
12047 }
12048
12049 void
12050 SettingsMenuIfReady()
12051 {
12052   if (second.lastPing != second.lastPong) {
12053     DisplayMessage("", _("Waiting for second chess program"));
12054     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12055     return;
12056   }
12057   ThawUI();
12058   DisplayMessage("", "");
12059   SettingsPopUp(&second);
12060 }
12061
12062 int
12063 WaitForSecond(DelayedEventCallback retry)
12064 {
12065     if (second.pr == NULL) {
12066         StartChessProgram(&second);
12067         if (second.protocolVersion == 1) {
12068           retry();
12069         } else {
12070           /* kludge: allow timeout for initial "feature" command */
12071           FreezeUI();
12072           DisplayMessage("", _("Starting second chess program"));
12073           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12074         }
12075         return 1;
12076     }
12077     return 0;
12078 }
12079
12080 void
12081 TwoMachinesEvent P((void))
12082 {
12083     int i;
12084     char buf[MSG_SIZ];
12085     ChessProgramState *onmove;
12086     char *bookHit = NULL;
12087     static int stalling = 0;
12088
12089     if (appData.noChessProgram) return;
12090
12091     switch (gameMode) {
12092       case TwoMachinesPlay:
12093         return;
12094       case MachinePlaysWhite:
12095       case MachinePlaysBlack:
12096         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12097             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12098             return;
12099         }
12100         /* fall through */
12101       case BeginningOfGame:
12102       case PlayFromGameFile:
12103       case EndOfGame:
12104         EditGameEvent();
12105         if (gameMode != EditGame) return;
12106         break;
12107       case EditPosition:
12108         EditPositionDone(TRUE);
12109         break;
12110       case AnalyzeMode:
12111       case AnalyzeFile:
12112         ExitAnalyzeMode();
12113         break;
12114       case EditGame:
12115       default:
12116         break;
12117     }
12118
12119 //    forwardMostMove = currentMove;
12120     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12121     ResurrectChessProgram();    /* in case first program isn't running */
12122
12123     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12124     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12125       DisplayMessage("", _("Waiting for first chess program"));
12126       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12127       return;
12128     }
12129     if(!stalling) {
12130       InitChessProgram(&second, FALSE);
12131       SendToProgram("force\n", &second);
12132     }
12133     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12134       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12135       stalling = 1;
12136       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12137       return;
12138     }
12139     stalling = 0;
12140     DisplayMessage("", "");
12141     if (startedFromSetupPosition) {
12142         SendBoard(&second, backwardMostMove);
12143     if (appData.debugMode) {
12144         fprintf(debugFP, "Two Machines\n");
12145     }
12146     }
12147     for (i = backwardMostMove; i < forwardMostMove; i++) {
12148         SendMoveToProgram(i, &second);
12149     }
12150
12151     gameMode = TwoMachinesPlay;
12152     pausing = FALSE;
12153     ModeHighlight();
12154     SetGameInfo();
12155     DisplayTwoMachinesTitle();
12156     firstMove = TRUE;
12157     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12158         onmove = &first;
12159     } else {
12160         onmove = &second;
12161     }
12162
12163     SendToProgram(first.computerString, &first);
12164     if (first.sendName) {
12165       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12166       SendToProgram(buf, &first);
12167     }
12168     SendToProgram(second.computerString, &second);
12169     if (second.sendName) {
12170       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12171       SendToProgram(buf, &second);
12172     }
12173
12174     ResetClocks();
12175     if (!first.sendTime || !second.sendTime) {
12176         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12177         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12178     }
12179     if (onmove->sendTime) {
12180       if (onmove->useColors) {
12181         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12182       }
12183       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12184     }
12185     if (onmove->useColors) {
12186       SendToProgram(onmove->twoMachinesColor, onmove);
12187     }
12188     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12189 //    SendToProgram("go\n", onmove);
12190     onmove->maybeThinking = TRUE;
12191     SetMachineThinkingEnables();
12192
12193     StartClocks();
12194
12195     if(bookHit) { // [HGM] book: simulate book reply
12196         static char bookMove[MSG_SIZ]; // a bit generous?
12197
12198         programStats.nodes = programStats.depth = programStats.time =
12199         programStats.score = programStats.got_only_move = 0;
12200         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12201
12202         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12203         strcat(bookMove, bookHit);
12204         savedMessage = bookMove; // args for deferred call
12205         savedState = onmove;
12206         ScheduleDelayedEvent(DeferredBookMove, 1);
12207     }
12208 }
12209
12210 void
12211 TrainingEvent()
12212 {
12213     if (gameMode == Training) {
12214       SetTrainingModeOff();
12215       gameMode = PlayFromGameFile;
12216       DisplayMessage("", _("Training mode off"));
12217     } else {
12218       gameMode = Training;
12219       animateTraining = appData.animate;
12220
12221       /* make sure we are not already at the end of the game */
12222       if (currentMove < forwardMostMove) {
12223         SetTrainingModeOn();
12224         DisplayMessage("", _("Training mode on"));
12225       } else {
12226         gameMode = PlayFromGameFile;
12227         DisplayError(_("Already at end of game"), 0);
12228       }
12229     }
12230     ModeHighlight();
12231 }
12232
12233 void
12234 IcsClientEvent()
12235 {
12236     if (!appData.icsActive) return;
12237     switch (gameMode) {
12238       case IcsPlayingWhite:
12239       case IcsPlayingBlack:
12240       case IcsObserving:
12241       case IcsIdle:
12242       case BeginningOfGame:
12243       case IcsExamining:
12244         return;
12245
12246       case EditGame:
12247         break;
12248
12249       case EditPosition:
12250         EditPositionDone(TRUE);
12251         break;
12252
12253       case AnalyzeMode:
12254       case AnalyzeFile:
12255         ExitAnalyzeMode();
12256         break;
12257
12258       default:
12259         EditGameEvent();
12260         break;
12261     }
12262
12263     gameMode = IcsIdle;
12264     ModeHighlight();
12265     return;
12266 }
12267
12268
12269 void
12270 EditGameEvent()
12271 {
12272     int i;
12273
12274     switch (gameMode) {
12275       case Training:
12276         SetTrainingModeOff();
12277         break;
12278       case MachinePlaysWhite:
12279       case MachinePlaysBlack:
12280       case BeginningOfGame:
12281         SendToProgram("force\n", &first);
12282         SetUserThinkingEnables();
12283         break;
12284       case PlayFromGameFile:
12285         (void) StopLoadGameTimer();
12286         if (gameFileFP != NULL) {
12287             gameFileFP = NULL;
12288         }
12289         break;
12290       case EditPosition:
12291         EditPositionDone(TRUE);
12292         break;
12293       case AnalyzeMode:
12294       case AnalyzeFile:
12295         ExitAnalyzeMode();
12296         SendToProgram("force\n", &first);
12297         break;
12298       case TwoMachinesPlay:
12299         GameEnds(EndOfFile, NULL, GE_PLAYER);
12300         ResurrectChessProgram();
12301         SetUserThinkingEnables();
12302         break;
12303       case EndOfGame:
12304         ResurrectChessProgram();
12305         break;
12306       case IcsPlayingBlack:
12307       case IcsPlayingWhite:
12308         DisplayError(_("Warning: You are still playing a game"), 0);
12309         break;
12310       case IcsObserving:
12311         DisplayError(_("Warning: You are still observing a game"), 0);
12312         break;
12313       case IcsExamining:
12314         DisplayError(_("Warning: You are still examining a game"), 0);
12315         break;
12316       case IcsIdle:
12317         break;
12318       case EditGame:
12319       default:
12320         return;
12321     }
12322
12323     pausing = FALSE;
12324     StopClocks();
12325     first.offeredDraw = second.offeredDraw = 0;
12326
12327     if (gameMode == PlayFromGameFile) {
12328         whiteTimeRemaining = timeRemaining[0][currentMove];
12329         blackTimeRemaining = timeRemaining[1][currentMove];
12330         DisplayTitle("");
12331     }
12332
12333     if (gameMode == MachinePlaysWhite ||
12334         gameMode == MachinePlaysBlack ||
12335         gameMode == TwoMachinesPlay ||
12336         gameMode == EndOfGame) {
12337         i = forwardMostMove;
12338         while (i > currentMove) {
12339             SendToProgram("undo\n", &first);
12340             i--;
12341         }
12342         whiteTimeRemaining = timeRemaining[0][currentMove];
12343         blackTimeRemaining = timeRemaining[1][currentMove];
12344         DisplayBothClocks();
12345         if (whiteFlag || blackFlag) {
12346             whiteFlag = blackFlag = 0;
12347         }
12348         DisplayTitle("");
12349     }
12350
12351     gameMode = EditGame;
12352     ModeHighlight();
12353     SetGameInfo();
12354 }
12355
12356
12357 void
12358 EditPositionEvent()
12359 {
12360     if (gameMode == EditPosition) {
12361         EditGameEvent();
12362         return;
12363     }
12364
12365     EditGameEvent();
12366     if (gameMode != EditGame) return;
12367
12368     gameMode = EditPosition;
12369     ModeHighlight();
12370     SetGameInfo();
12371     if (currentMove > 0)
12372       CopyBoard(boards[0], boards[currentMove]);
12373
12374     blackPlaysFirst = !WhiteOnMove(currentMove);
12375     ResetClocks();
12376     currentMove = forwardMostMove = backwardMostMove = 0;
12377     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12378     DisplayMove(-1);
12379 }
12380
12381 void
12382 ExitAnalyzeMode()
12383 {
12384     /* [DM] icsEngineAnalyze - possible call from other functions */
12385     if (appData.icsEngineAnalyze) {
12386         appData.icsEngineAnalyze = FALSE;
12387
12388         DisplayMessage("",_("Close ICS engine analyze..."));
12389     }
12390     if (first.analysisSupport && first.analyzing) {
12391       SendToProgram("exit\n", &first);
12392       first.analyzing = FALSE;
12393     }
12394     thinkOutput[0] = NULLCHAR;
12395 }
12396
12397 void
12398 EditPositionDone(Boolean fakeRights)
12399 {
12400     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12401
12402     startedFromSetupPosition = TRUE;
12403     InitChessProgram(&first, FALSE);
12404     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12405       boards[0][EP_STATUS] = EP_NONE;
12406       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12407     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12408         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12409         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12410       } else boards[0][CASTLING][2] = NoRights;
12411     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12412         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12413         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12414       } else boards[0][CASTLING][5] = NoRights;
12415     }
12416     SendToProgram("force\n", &first);
12417     if (blackPlaysFirst) {
12418         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12419         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12420         currentMove = forwardMostMove = backwardMostMove = 1;
12421         CopyBoard(boards[1], boards[0]);
12422     } else {
12423         currentMove = forwardMostMove = backwardMostMove = 0;
12424     }
12425     SendBoard(&first, forwardMostMove);
12426     if (appData.debugMode) {
12427         fprintf(debugFP, "EditPosDone\n");
12428     }
12429     DisplayTitle("");
12430     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12431     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12432     gameMode = EditGame;
12433     ModeHighlight();
12434     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12435     ClearHighlights(); /* [AS] */
12436 }
12437
12438 /* Pause for `ms' milliseconds */
12439 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12440 void
12441 TimeDelay(ms)
12442      long ms;
12443 {
12444     TimeMark m1, m2;
12445
12446     GetTimeMark(&m1);
12447     do {
12448         GetTimeMark(&m2);
12449     } while (SubtractTimeMarks(&m2, &m1) < ms);
12450 }
12451
12452 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12453 void
12454 SendMultiLineToICS(buf)
12455      char *buf;
12456 {
12457     char temp[MSG_SIZ+1], *p;
12458     int len;
12459
12460     len = strlen(buf);
12461     if (len > MSG_SIZ)
12462       len = MSG_SIZ;
12463
12464     strncpy(temp, buf, len);
12465     temp[len] = 0;
12466
12467     p = temp;
12468     while (*p) {
12469         if (*p == '\n' || *p == '\r')
12470           *p = ' ';
12471         ++p;
12472     }
12473
12474     strcat(temp, "\n");
12475     SendToICS(temp);
12476     SendToPlayer(temp, strlen(temp));
12477 }
12478
12479 void
12480 SetWhiteToPlayEvent()
12481 {
12482     if (gameMode == EditPosition) {
12483         blackPlaysFirst = FALSE;
12484         DisplayBothClocks();    /* works because currentMove is 0 */
12485     } else if (gameMode == IcsExamining) {
12486         SendToICS(ics_prefix);
12487         SendToICS("tomove white\n");
12488     }
12489 }
12490
12491 void
12492 SetBlackToPlayEvent()
12493 {
12494     if (gameMode == EditPosition) {
12495         blackPlaysFirst = TRUE;
12496         currentMove = 1;        /* kludge */
12497         DisplayBothClocks();
12498         currentMove = 0;
12499     } else if (gameMode == IcsExamining) {
12500         SendToICS(ics_prefix);
12501         SendToICS("tomove black\n");
12502     }
12503 }
12504
12505 void
12506 EditPositionMenuEvent(selection, x, y)
12507      ChessSquare selection;
12508      int x, y;
12509 {
12510     char buf[MSG_SIZ];
12511     ChessSquare piece = boards[0][y][x];
12512
12513     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12514
12515     switch (selection) {
12516       case ClearBoard:
12517         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12518             SendToICS(ics_prefix);
12519             SendToICS("bsetup clear\n");
12520         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12521             SendToICS(ics_prefix);
12522             SendToICS("clearboard\n");
12523         } else {
12524             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12525                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12526                 for (y = 0; y < BOARD_HEIGHT; y++) {
12527                     if (gameMode == IcsExamining) {
12528                         if (boards[currentMove][y][x] != EmptySquare) {
12529                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12530                                     AAA + x, ONE + y);
12531                             SendToICS(buf);
12532                         }
12533                     } else {
12534                         boards[0][y][x] = p;
12535                     }
12536                 }
12537             }
12538         }
12539         if (gameMode == EditPosition) {
12540             DrawPosition(FALSE, boards[0]);
12541         }
12542         break;
12543
12544       case WhitePlay:
12545         SetWhiteToPlayEvent();
12546         break;
12547
12548       case BlackPlay:
12549         SetBlackToPlayEvent();
12550         break;
12551
12552       case EmptySquare:
12553         if (gameMode == IcsExamining) {
12554             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12555             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12556             SendToICS(buf);
12557         } else {
12558             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12559                 if(x == BOARD_LEFT-2) {
12560                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12561                     boards[0][y][1] = 0;
12562                 } else
12563                 if(x == BOARD_RGHT+1) {
12564                     if(y >= gameInfo.holdingsSize) break;
12565                     boards[0][y][BOARD_WIDTH-2] = 0;
12566                 } else break;
12567             }
12568             boards[0][y][x] = EmptySquare;
12569             DrawPosition(FALSE, boards[0]);
12570         }
12571         break;
12572
12573       case PromotePiece:
12574         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12575            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12576             selection = (ChessSquare) (PROMOTED piece);
12577         } else if(piece == EmptySquare) selection = WhiteSilver;
12578         else selection = (ChessSquare)((int)piece - 1);
12579         goto defaultlabel;
12580
12581       case DemotePiece:
12582         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12583            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12584             selection = (ChessSquare) (DEMOTED piece);
12585         } else if(piece == EmptySquare) selection = BlackSilver;
12586         else selection = (ChessSquare)((int)piece + 1);
12587         goto defaultlabel;
12588
12589       case WhiteQueen:
12590       case BlackQueen:
12591         if(gameInfo.variant == VariantShatranj ||
12592            gameInfo.variant == VariantXiangqi  ||
12593            gameInfo.variant == VariantCourier  ||
12594            gameInfo.variant == VariantMakruk     )
12595             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12596         goto defaultlabel;
12597
12598       case WhiteKing:
12599       case BlackKing:
12600         if(gameInfo.variant == VariantXiangqi)
12601             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12602         if(gameInfo.variant == VariantKnightmate)
12603             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12604       default:
12605         defaultlabel:
12606         if (gameMode == IcsExamining) {
12607             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12608             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12609                      PieceToChar(selection), AAA + x, ONE + y);
12610             SendToICS(buf);
12611         } else {
12612             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12613                 int n;
12614                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12615                     n = PieceToNumber(selection - BlackPawn);
12616                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12617                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12618                     boards[0][BOARD_HEIGHT-1-n][1]++;
12619                 } else
12620                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12621                     n = PieceToNumber(selection);
12622                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12623                     boards[0][n][BOARD_WIDTH-1] = selection;
12624                     boards[0][n][BOARD_WIDTH-2]++;
12625                 }
12626             } else
12627             boards[0][y][x] = selection;
12628             DrawPosition(TRUE, boards[0]);
12629         }
12630         break;
12631     }
12632 }
12633
12634
12635 void
12636 DropMenuEvent(selection, x, y)
12637      ChessSquare selection;
12638      int x, y;
12639 {
12640     ChessMove moveType;
12641
12642     switch (gameMode) {
12643       case IcsPlayingWhite:
12644       case MachinePlaysBlack:
12645         if (!WhiteOnMove(currentMove)) {
12646             DisplayMoveError(_("It is Black's turn"));
12647             return;
12648         }
12649         moveType = WhiteDrop;
12650         break;
12651       case IcsPlayingBlack:
12652       case MachinePlaysWhite:
12653         if (WhiteOnMove(currentMove)) {
12654             DisplayMoveError(_("It is White's turn"));
12655             return;
12656         }
12657         moveType = BlackDrop;
12658         break;
12659       case EditGame:
12660         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12661         break;
12662       default:
12663         return;
12664     }
12665
12666     if (moveType == BlackDrop && selection < BlackPawn) {
12667       selection = (ChessSquare) ((int) selection
12668                                  + (int) BlackPawn - (int) WhitePawn);
12669     }
12670     if (boards[currentMove][y][x] != EmptySquare) {
12671         DisplayMoveError(_("That square is occupied"));
12672         return;
12673     }
12674
12675     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12676 }
12677
12678 void
12679 AcceptEvent()
12680 {
12681     /* Accept a pending offer of any kind from opponent */
12682
12683     if (appData.icsActive) {
12684         SendToICS(ics_prefix);
12685         SendToICS("accept\n");
12686     } else if (cmailMsgLoaded) {
12687         if (currentMove == cmailOldMove &&
12688             commentList[cmailOldMove] != NULL &&
12689             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12690                    "Black offers a draw" : "White offers a draw")) {
12691             TruncateGame();
12692             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12693             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12694         } else {
12695             DisplayError(_("There is no pending offer on this move"), 0);
12696             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12697         }
12698     } else {
12699         /* Not used for offers from chess program */
12700     }
12701 }
12702
12703 void
12704 DeclineEvent()
12705 {
12706     /* Decline a pending offer of any kind from opponent */
12707
12708     if (appData.icsActive) {
12709         SendToICS(ics_prefix);
12710         SendToICS("decline\n");
12711     } else if (cmailMsgLoaded) {
12712         if (currentMove == cmailOldMove &&
12713             commentList[cmailOldMove] != NULL &&
12714             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12715                    "Black offers a draw" : "White offers a draw")) {
12716 #ifdef NOTDEF
12717             AppendComment(cmailOldMove, "Draw declined", TRUE);
12718             DisplayComment(cmailOldMove - 1, "Draw declined");
12719 #endif /*NOTDEF*/
12720         } else {
12721             DisplayError(_("There is no pending offer on this move"), 0);
12722         }
12723     } else {
12724         /* Not used for offers from chess program */
12725     }
12726 }
12727
12728 void
12729 RematchEvent()
12730 {
12731     /* Issue ICS rematch command */
12732     if (appData.icsActive) {
12733         SendToICS(ics_prefix);
12734         SendToICS("rematch\n");
12735     }
12736 }
12737
12738 void
12739 CallFlagEvent()
12740 {
12741     /* Call your opponent's flag (claim a win on time) */
12742     if (appData.icsActive) {
12743         SendToICS(ics_prefix);
12744         SendToICS("flag\n");
12745     } else {
12746         switch (gameMode) {
12747           default:
12748             return;
12749           case MachinePlaysWhite:
12750             if (whiteFlag) {
12751                 if (blackFlag)
12752                   GameEnds(GameIsDrawn, "Both players ran out of time",
12753                            GE_PLAYER);
12754                 else
12755                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12756             } else {
12757                 DisplayError(_("Your opponent is not out of time"), 0);
12758             }
12759             break;
12760           case MachinePlaysBlack:
12761             if (blackFlag) {
12762                 if (whiteFlag)
12763                   GameEnds(GameIsDrawn, "Both players ran out of time",
12764                            GE_PLAYER);
12765                 else
12766                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12767             } else {
12768                 DisplayError(_("Your opponent is not out of time"), 0);
12769             }
12770             break;
12771         }
12772     }
12773 }
12774
12775 void
12776 ClockClick(int which)
12777 {       // [HGM] code moved to back-end from winboard.c
12778         if(which) { // black clock
12779           if (gameMode == EditPosition || gameMode == IcsExamining) {
12780             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12781             SetBlackToPlayEvent();
12782           } else if (gameMode == EditGame || shiftKey) {
12783             AdjustClock(which, -1);
12784           } else if (gameMode == IcsPlayingWhite ||
12785                      gameMode == MachinePlaysBlack) {
12786             CallFlagEvent();
12787           }
12788         } else { // white clock
12789           if (gameMode == EditPosition || gameMode == IcsExamining) {
12790             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12791             SetWhiteToPlayEvent();
12792           } else if (gameMode == EditGame || shiftKey) {
12793             AdjustClock(which, -1);
12794           } else if (gameMode == IcsPlayingBlack ||
12795                    gameMode == MachinePlaysWhite) {
12796             CallFlagEvent();
12797           }
12798         }
12799 }
12800
12801 void
12802 DrawEvent()
12803 {
12804     /* Offer draw or accept pending draw offer from opponent */
12805
12806     if (appData.icsActive) {
12807         /* Note: tournament rules require draw offers to be
12808            made after you make your move but before you punch
12809            your clock.  Currently ICS doesn't let you do that;
12810            instead, you immediately punch your clock after making
12811            a move, but you can offer a draw at any time. */
12812
12813         SendToICS(ics_prefix);
12814         SendToICS("draw\n");
12815         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12816     } else if (cmailMsgLoaded) {
12817         if (currentMove == cmailOldMove &&
12818             commentList[cmailOldMove] != NULL &&
12819             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12820                    "Black offers a draw" : "White offers a draw")) {
12821             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12822             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12823         } else if (currentMove == cmailOldMove + 1) {
12824             char *offer = WhiteOnMove(cmailOldMove) ?
12825               "White offers a draw" : "Black offers a draw";
12826             AppendComment(currentMove, offer, TRUE);
12827             DisplayComment(currentMove - 1, offer);
12828             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12829         } else {
12830             DisplayError(_("You must make your move before offering a draw"), 0);
12831             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12832         }
12833     } else if (first.offeredDraw) {
12834         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12835     } else {
12836         if (first.sendDrawOffers) {
12837             SendToProgram("draw\n", &first);
12838             userOfferedDraw = TRUE;
12839         }
12840     }
12841 }
12842
12843 void
12844 AdjournEvent()
12845 {
12846     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12847
12848     if (appData.icsActive) {
12849         SendToICS(ics_prefix);
12850         SendToICS("adjourn\n");
12851     } else {
12852         /* Currently GNU Chess doesn't offer or accept Adjourns */
12853     }
12854 }
12855
12856
12857 void
12858 AbortEvent()
12859 {
12860     /* Offer Abort or accept pending Abort offer from opponent */
12861
12862     if (appData.icsActive) {
12863         SendToICS(ics_prefix);
12864         SendToICS("abort\n");
12865     } else {
12866         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12867     }
12868 }
12869
12870 void
12871 ResignEvent()
12872 {
12873     /* Resign.  You can do this even if it's not your turn. */
12874
12875     if (appData.icsActive) {
12876         SendToICS(ics_prefix);
12877         SendToICS("resign\n");
12878     } else {
12879         switch (gameMode) {
12880           case MachinePlaysWhite:
12881             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12882             break;
12883           case MachinePlaysBlack:
12884             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12885             break;
12886           case EditGame:
12887             if (cmailMsgLoaded) {
12888                 TruncateGame();
12889                 if (WhiteOnMove(cmailOldMove)) {
12890                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12891                 } else {
12892                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12893                 }
12894                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12895             }
12896             break;
12897           default:
12898             break;
12899         }
12900     }
12901 }
12902
12903
12904 void
12905 StopObservingEvent()
12906 {
12907     /* Stop observing current games */
12908     SendToICS(ics_prefix);
12909     SendToICS("unobserve\n");
12910 }
12911
12912 void
12913 StopExaminingEvent()
12914 {
12915     /* Stop observing current game */
12916     SendToICS(ics_prefix);
12917     SendToICS("unexamine\n");
12918 }
12919
12920 void
12921 ForwardInner(target)
12922      int target;
12923 {
12924     int limit;
12925
12926     if (appData.debugMode)
12927         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12928                 target, currentMove, forwardMostMove);
12929
12930     if (gameMode == EditPosition)
12931       return;
12932
12933     if (gameMode == PlayFromGameFile && !pausing)
12934       PauseEvent();
12935
12936     if (gameMode == IcsExamining && pausing)
12937       limit = pauseExamForwardMostMove;
12938     else
12939       limit = forwardMostMove;
12940
12941     if (target > limit) target = limit;
12942
12943     if (target > 0 && moveList[target - 1][0]) {
12944         int fromX, fromY, toX, toY;
12945         toX = moveList[target - 1][2] - AAA;
12946         toY = moveList[target - 1][3] - ONE;
12947         if (moveList[target - 1][1] == '@') {
12948             if (appData.highlightLastMove) {
12949                 SetHighlights(-1, -1, toX, toY);
12950             }
12951         } else {
12952             fromX = moveList[target - 1][0] - AAA;
12953             fromY = moveList[target - 1][1] - ONE;
12954             if (target == currentMove + 1) {
12955                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12956             }
12957             if (appData.highlightLastMove) {
12958                 SetHighlights(fromX, fromY, toX, toY);
12959             }
12960         }
12961     }
12962     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12963         gameMode == Training || gameMode == PlayFromGameFile ||
12964         gameMode == AnalyzeFile) {
12965         while (currentMove < target) {
12966             SendMoveToProgram(currentMove++, &first);
12967         }
12968     } else {
12969         currentMove = target;
12970     }
12971
12972     if (gameMode == EditGame || gameMode == EndOfGame) {
12973         whiteTimeRemaining = timeRemaining[0][currentMove];
12974         blackTimeRemaining = timeRemaining[1][currentMove];
12975     }
12976     DisplayBothClocks();
12977     DisplayMove(currentMove - 1);
12978     DrawPosition(FALSE, boards[currentMove]);
12979     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12980     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12981         DisplayComment(currentMove - 1, commentList[currentMove]);
12982     }
12983 }
12984
12985
12986 void
12987 ForwardEvent()
12988 {
12989     if (gameMode == IcsExamining && !pausing) {
12990         SendToICS(ics_prefix);
12991         SendToICS("forward\n");
12992     } else {
12993         ForwardInner(currentMove + 1);
12994     }
12995 }
12996
12997 void
12998 ToEndEvent()
12999 {
13000     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13001         /* to optimze, we temporarily turn off analysis mode while we feed
13002          * the remaining moves to the engine. Otherwise we get analysis output
13003          * after each move.
13004          */
13005         if (first.analysisSupport) {
13006           SendToProgram("exit\nforce\n", &first);
13007           first.analyzing = FALSE;
13008         }
13009     }
13010
13011     if (gameMode == IcsExamining && !pausing) {
13012         SendToICS(ics_prefix);
13013         SendToICS("forward 999999\n");
13014     } else {
13015         ForwardInner(forwardMostMove);
13016     }
13017
13018     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13019         /* we have fed all the moves, so reactivate analysis mode */
13020         SendToProgram("analyze\n", &first);
13021         first.analyzing = TRUE;
13022         /*first.maybeThinking = TRUE;*/
13023         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13024     }
13025 }
13026
13027 void
13028 BackwardInner(target)
13029      int target;
13030 {
13031     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13032
13033     if (appData.debugMode)
13034         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13035                 target, currentMove, forwardMostMove);
13036
13037     if (gameMode == EditPosition) return;
13038     if (currentMove <= backwardMostMove) {
13039         ClearHighlights();
13040         DrawPosition(full_redraw, boards[currentMove]);
13041         return;
13042     }
13043     if (gameMode == PlayFromGameFile && !pausing)
13044       PauseEvent();
13045
13046     if (moveList[target][0]) {
13047         int fromX, fromY, toX, toY;
13048         toX = moveList[target][2] - AAA;
13049         toY = moveList[target][3] - ONE;
13050         if (moveList[target][1] == '@') {
13051             if (appData.highlightLastMove) {
13052                 SetHighlights(-1, -1, toX, toY);
13053             }
13054         } else {
13055             fromX = moveList[target][0] - AAA;
13056             fromY = moveList[target][1] - ONE;
13057             if (target == currentMove - 1) {
13058                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13059             }
13060             if (appData.highlightLastMove) {
13061                 SetHighlights(fromX, fromY, toX, toY);
13062             }
13063         }
13064     }
13065     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13066         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13067         while (currentMove > target) {
13068             SendToProgram("undo\n", &first);
13069             currentMove--;
13070         }
13071     } else {
13072         currentMove = target;
13073     }
13074
13075     if (gameMode == EditGame || gameMode == EndOfGame) {
13076         whiteTimeRemaining = timeRemaining[0][currentMove];
13077         blackTimeRemaining = timeRemaining[1][currentMove];
13078     }
13079     DisplayBothClocks();
13080     DisplayMove(currentMove - 1);
13081     DrawPosition(full_redraw, boards[currentMove]);
13082     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13083     // [HGM] PV info: routine tests if comment empty
13084     DisplayComment(currentMove - 1, commentList[currentMove]);
13085 }
13086
13087 void
13088 BackwardEvent()
13089 {
13090     if (gameMode == IcsExamining && !pausing) {
13091         SendToICS(ics_prefix);
13092         SendToICS("backward\n");
13093     } else {
13094         BackwardInner(currentMove - 1);
13095     }
13096 }
13097
13098 void
13099 ToStartEvent()
13100 {
13101     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13102         /* to optimize, we temporarily turn off analysis mode while we undo
13103          * all the moves. Otherwise we get analysis output after each undo.
13104          */
13105         if (first.analysisSupport) {
13106           SendToProgram("exit\nforce\n", &first);
13107           first.analyzing = FALSE;
13108         }
13109     }
13110
13111     if (gameMode == IcsExamining && !pausing) {
13112         SendToICS(ics_prefix);
13113         SendToICS("backward 999999\n");
13114     } else {
13115         BackwardInner(backwardMostMove);
13116     }
13117
13118     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13119         /* we have fed all the moves, so reactivate analysis mode */
13120         SendToProgram("analyze\n", &first);
13121         first.analyzing = TRUE;
13122         /*first.maybeThinking = TRUE;*/
13123         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13124     }
13125 }
13126
13127 void
13128 ToNrEvent(int to)
13129 {
13130   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13131   if (to >= forwardMostMove) to = forwardMostMove;
13132   if (to <= backwardMostMove) to = backwardMostMove;
13133   if (to < currentMove) {
13134     BackwardInner(to);
13135   } else {
13136     ForwardInner(to);
13137   }
13138 }
13139
13140 void
13141 RevertEvent(Boolean annotate)
13142 {
13143     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13144         return;
13145     }
13146     if (gameMode != IcsExamining) {
13147         DisplayError(_("You are not examining a game"), 0);
13148         return;
13149     }
13150     if (pausing) {
13151         DisplayError(_("You can't revert while pausing"), 0);
13152         return;
13153     }
13154     SendToICS(ics_prefix);
13155     SendToICS("revert\n");
13156 }
13157
13158 void
13159 RetractMoveEvent()
13160 {
13161     switch (gameMode) {
13162       case MachinePlaysWhite:
13163       case MachinePlaysBlack:
13164         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13165             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13166             return;
13167         }
13168         if (forwardMostMove < 2) return;
13169         currentMove = forwardMostMove = forwardMostMove - 2;
13170         whiteTimeRemaining = timeRemaining[0][currentMove];
13171         blackTimeRemaining = timeRemaining[1][currentMove];
13172         DisplayBothClocks();
13173         DisplayMove(currentMove - 1);
13174         ClearHighlights();/*!! could figure this out*/
13175         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13176         SendToProgram("remove\n", &first);
13177         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13178         break;
13179
13180       case BeginningOfGame:
13181       default:
13182         break;
13183
13184       case IcsPlayingWhite:
13185       case IcsPlayingBlack:
13186         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13187             SendToICS(ics_prefix);
13188             SendToICS("takeback 2\n");
13189         } else {
13190             SendToICS(ics_prefix);
13191             SendToICS("takeback 1\n");
13192         }
13193         break;
13194     }
13195 }
13196
13197 void
13198 MoveNowEvent()
13199 {
13200     ChessProgramState *cps;
13201
13202     switch (gameMode) {
13203       case MachinePlaysWhite:
13204         if (!WhiteOnMove(forwardMostMove)) {
13205             DisplayError(_("It is your turn"), 0);
13206             return;
13207         }
13208         cps = &first;
13209         break;
13210       case MachinePlaysBlack:
13211         if (WhiteOnMove(forwardMostMove)) {
13212             DisplayError(_("It is your turn"), 0);
13213             return;
13214         }
13215         cps = &first;
13216         break;
13217       case TwoMachinesPlay:
13218         if (WhiteOnMove(forwardMostMove) ==
13219             (first.twoMachinesColor[0] == 'w')) {
13220             cps = &first;
13221         } else {
13222             cps = &second;
13223         }
13224         break;
13225       case BeginningOfGame:
13226       default:
13227         return;
13228     }
13229     SendToProgram("?\n", cps);
13230 }
13231
13232 void
13233 TruncateGameEvent()
13234 {
13235     EditGameEvent();
13236     if (gameMode != EditGame) return;
13237     TruncateGame();
13238 }
13239
13240 void
13241 TruncateGame()
13242 {
13243     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13244     if (forwardMostMove > currentMove) {
13245         if (gameInfo.resultDetails != NULL) {
13246             free(gameInfo.resultDetails);
13247             gameInfo.resultDetails = NULL;
13248             gameInfo.result = GameUnfinished;
13249         }
13250         forwardMostMove = currentMove;
13251         HistorySet(parseList, backwardMostMove, forwardMostMove,
13252                    currentMove-1);
13253     }
13254 }
13255
13256 void
13257 HintEvent()
13258 {
13259     if (appData.noChessProgram) return;
13260     switch (gameMode) {
13261       case MachinePlaysWhite:
13262         if (WhiteOnMove(forwardMostMove)) {
13263             DisplayError(_("Wait until your turn"), 0);
13264             return;
13265         }
13266         break;
13267       case BeginningOfGame:
13268       case MachinePlaysBlack:
13269         if (!WhiteOnMove(forwardMostMove)) {
13270             DisplayError(_("Wait until your turn"), 0);
13271             return;
13272         }
13273         break;
13274       default:
13275         DisplayError(_("No hint available"), 0);
13276         return;
13277     }
13278     SendToProgram("hint\n", &first);
13279     hintRequested = TRUE;
13280 }
13281
13282 void
13283 BookEvent()
13284 {
13285     if (appData.noChessProgram) return;
13286     switch (gameMode) {
13287       case MachinePlaysWhite:
13288         if (WhiteOnMove(forwardMostMove)) {
13289             DisplayError(_("Wait until your turn"), 0);
13290             return;
13291         }
13292         break;
13293       case BeginningOfGame:
13294       case MachinePlaysBlack:
13295         if (!WhiteOnMove(forwardMostMove)) {
13296             DisplayError(_("Wait until your turn"), 0);
13297             return;
13298         }
13299         break;
13300       case EditPosition:
13301         EditPositionDone(TRUE);
13302         break;
13303       case TwoMachinesPlay:
13304         return;
13305       default:
13306         break;
13307     }
13308     SendToProgram("bk\n", &first);
13309     bookOutput[0] = NULLCHAR;
13310     bookRequested = TRUE;
13311 }
13312
13313 void
13314 AboutGameEvent()
13315 {
13316     char *tags = PGNTags(&gameInfo);
13317     TagsPopUp(tags, CmailMsg());
13318     free(tags);
13319 }
13320
13321 /* end button procedures */
13322
13323 void
13324 PrintPosition(fp, move)
13325      FILE *fp;
13326      int move;
13327 {
13328     int i, j;
13329
13330     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13331         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13332             char c = PieceToChar(boards[move][i][j]);
13333             fputc(c == 'x' ? '.' : c, fp);
13334             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13335         }
13336     }
13337     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13338       fprintf(fp, "white to play\n");
13339     else
13340       fprintf(fp, "black to play\n");
13341 }
13342
13343 void
13344 PrintOpponents(fp)
13345      FILE *fp;
13346 {
13347     if (gameInfo.white != NULL) {
13348         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13349     } else {
13350         fprintf(fp, "\n");
13351     }
13352 }
13353
13354 /* Find last component of program's own name, using some heuristics */
13355 void
13356 TidyProgramName(prog, host, buf)
13357      char *prog, *host, buf[MSG_SIZ];
13358 {
13359     char *p, *q;
13360     int local = (strcmp(host, "localhost") == 0);
13361     while (!local && (p = strchr(prog, ';')) != NULL) {
13362         p++;
13363         while (*p == ' ') p++;
13364         prog = p;
13365     }
13366     if (*prog == '"' || *prog == '\'') {
13367         q = strchr(prog + 1, *prog);
13368     } else {
13369         q = strchr(prog, ' ');
13370     }
13371     if (q == NULL) q = prog + strlen(prog);
13372     p = q;
13373     while (p >= prog && *p != '/' && *p != '\\') p--;
13374     p++;
13375     if(p == prog && *p == '"') p++;
13376     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13377     memcpy(buf, p, q - p);
13378     buf[q - p] = NULLCHAR;
13379     if (!local) {
13380         strcat(buf, "@");
13381         strcat(buf, host);
13382     }
13383 }
13384
13385 char *
13386 TimeControlTagValue()
13387 {
13388     char buf[MSG_SIZ];
13389     if (!appData.clockMode) {
13390       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13391     } else if (movesPerSession > 0) {
13392       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13393     } else if (timeIncrement == 0) {
13394       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13395     } else {
13396       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13397     }
13398     return StrSave(buf);
13399 }
13400
13401 void
13402 SetGameInfo()
13403 {
13404     /* This routine is used only for certain modes */
13405     VariantClass v = gameInfo.variant;
13406     ChessMove r = GameUnfinished;
13407     char *p = NULL;
13408
13409     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13410         r = gameInfo.result;
13411         p = gameInfo.resultDetails;
13412         gameInfo.resultDetails = NULL;
13413     }
13414     ClearGameInfo(&gameInfo);
13415     gameInfo.variant = v;
13416
13417     switch (gameMode) {
13418       case MachinePlaysWhite:
13419         gameInfo.event = StrSave( appData.pgnEventHeader );
13420         gameInfo.site = StrSave(HostName());
13421         gameInfo.date = PGNDate();
13422         gameInfo.round = StrSave("-");
13423         gameInfo.white = StrSave(first.tidy);
13424         gameInfo.black = StrSave(UserName());
13425         gameInfo.timeControl = TimeControlTagValue();
13426         break;
13427
13428       case MachinePlaysBlack:
13429         gameInfo.event = StrSave( appData.pgnEventHeader );
13430         gameInfo.site = StrSave(HostName());
13431         gameInfo.date = PGNDate();
13432         gameInfo.round = StrSave("-");
13433         gameInfo.white = StrSave(UserName());
13434         gameInfo.black = StrSave(first.tidy);
13435         gameInfo.timeControl = TimeControlTagValue();
13436         break;
13437
13438       case TwoMachinesPlay:
13439         gameInfo.event = StrSave( appData.pgnEventHeader );
13440         gameInfo.site = StrSave(HostName());
13441         gameInfo.date = PGNDate();
13442         if (matchGame > 0) {
13443             char buf[MSG_SIZ];
13444             snprintf(buf, MSG_SIZ, "%d", matchGame);
13445             gameInfo.round = StrSave(buf);
13446         } else {
13447             gameInfo.round = StrSave("-");
13448         }
13449         if (first.twoMachinesColor[0] == 'w') {
13450             gameInfo.white = StrSave(first.tidy);
13451             gameInfo.black = StrSave(second.tidy);
13452         } else {
13453             gameInfo.white = StrSave(second.tidy);
13454             gameInfo.black = StrSave(first.tidy);
13455         }
13456         gameInfo.timeControl = TimeControlTagValue();
13457         break;
13458
13459       case EditGame:
13460         gameInfo.event = StrSave("Edited game");
13461         gameInfo.site = StrSave(HostName());
13462         gameInfo.date = PGNDate();
13463         gameInfo.round = StrSave("-");
13464         gameInfo.white = StrSave("-");
13465         gameInfo.black = StrSave("-");
13466         gameInfo.result = r;
13467         gameInfo.resultDetails = p;
13468         break;
13469
13470       case EditPosition:
13471         gameInfo.event = StrSave("Edited position");
13472         gameInfo.site = StrSave(HostName());
13473         gameInfo.date = PGNDate();
13474         gameInfo.round = StrSave("-");
13475         gameInfo.white = StrSave("-");
13476         gameInfo.black = StrSave("-");
13477         break;
13478
13479       case IcsPlayingWhite:
13480       case IcsPlayingBlack:
13481       case IcsObserving:
13482       case IcsExamining:
13483         break;
13484
13485       case PlayFromGameFile:
13486         gameInfo.event = StrSave("Game from non-PGN file");
13487         gameInfo.site = StrSave(HostName());
13488         gameInfo.date = PGNDate();
13489         gameInfo.round = StrSave("-");
13490         gameInfo.white = StrSave("?");
13491         gameInfo.black = StrSave("?");
13492         break;
13493
13494       default:
13495         break;
13496     }
13497 }
13498
13499 void
13500 ReplaceComment(index, text)
13501      int index;
13502      char *text;
13503 {
13504     int len;
13505     char *p;
13506     float score;
13507
13508     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13509        pvInfoList[index-1].depth == len &&
13510        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13511        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13512     while (*text == '\n') text++;
13513     len = strlen(text);
13514     while (len > 0 && text[len - 1] == '\n') len--;
13515
13516     if (commentList[index] != NULL)
13517       free(commentList[index]);
13518
13519     if (len == 0) {
13520         commentList[index] = NULL;
13521         return;
13522     }
13523   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13524       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13525       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13526     commentList[index] = (char *) malloc(len + 2);
13527     strncpy(commentList[index], text, len);
13528     commentList[index][len] = '\n';
13529     commentList[index][len + 1] = NULLCHAR;
13530   } else {
13531     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13532     char *p;
13533     commentList[index] = (char *) malloc(len + 7);
13534     safeStrCpy(commentList[index], "{\n", 3);
13535     safeStrCpy(commentList[index]+2, text, len+1);
13536     commentList[index][len+2] = NULLCHAR;
13537     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13538     strcat(commentList[index], "\n}\n");
13539   }
13540 }
13541
13542 void
13543 CrushCRs(text)
13544      char *text;
13545 {
13546   char *p = text;
13547   char *q = text;
13548   char ch;
13549
13550   do {
13551     ch = *p++;
13552     if (ch == '\r') continue;
13553     *q++ = ch;
13554   } while (ch != '\0');
13555 }
13556
13557 void
13558 AppendComment(index, text, addBraces)
13559      int index;
13560      char *text;
13561      Boolean addBraces; // [HGM] braces: tells if we should add {}
13562 {
13563     int oldlen, len;
13564     char *old;
13565
13566 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13567     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13568
13569     CrushCRs(text);
13570     while (*text == '\n') text++;
13571     len = strlen(text);
13572     while (len > 0 && text[len - 1] == '\n') len--;
13573
13574     if (len == 0) return;
13575
13576     if (commentList[index] != NULL) {
13577         old = commentList[index];
13578         oldlen = strlen(old);
13579         while(commentList[index][oldlen-1] ==  '\n')
13580           commentList[index][--oldlen] = NULLCHAR;
13581         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13582         safeStrCpy(commentList[index], old, oldlen + len + 6);
13583         free(old);
13584         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13585         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13586           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13587           while (*text == '\n') { text++; len--; }
13588           commentList[index][--oldlen] = NULLCHAR;
13589       }
13590         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13591         else          strcat(commentList[index], "\n");
13592         strcat(commentList[index], text);
13593         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13594         else          strcat(commentList[index], "\n");
13595     } else {
13596         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13597         if(addBraces)
13598           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13599         else commentList[index][0] = NULLCHAR;
13600         strcat(commentList[index], text);
13601         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13602         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13603     }
13604 }
13605
13606 static char * FindStr( char * text, char * sub_text )
13607 {
13608     char * result = strstr( text, sub_text );
13609
13610     if( result != NULL ) {
13611         result += strlen( sub_text );
13612     }
13613
13614     return result;
13615 }
13616
13617 /* [AS] Try to extract PV info from PGN comment */
13618 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13619 char *GetInfoFromComment( int index, char * text )
13620 {
13621     char * sep = text, *p;
13622
13623     if( text != NULL && index > 0 ) {
13624         int score = 0;
13625         int depth = 0;
13626         int time = -1, sec = 0, deci;
13627         char * s_eval = FindStr( text, "[%eval " );
13628         char * s_emt = FindStr( text, "[%emt " );
13629
13630         if( s_eval != NULL || s_emt != NULL ) {
13631             /* New style */
13632             char delim;
13633
13634             if( s_eval != NULL ) {
13635                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13636                     return text;
13637                 }
13638
13639                 if( delim != ']' ) {
13640                     return text;
13641                 }
13642             }
13643
13644             if( s_emt != NULL ) {
13645             }
13646                 return text;
13647         }
13648         else {
13649             /* We expect something like: [+|-]nnn.nn/dd */
13650             int score_lo = 0;
13651
13652             if(*text != '{') return text; // [HGM] braces: must be normal comment
13653
13654             sep = strchr( text, '/' );
13655             if( sep == NULL || sep < (text+4) ) {
13656                 return text;
13657             }
13658
13659             p = text;
13660             if(p[1] == '(') { // comment starts with PV
13661                p = strchr(p, ')'); // locate end of PV
13662                if(p == NULL || sep < p+5) return text;
13663                // at this point we have something like "{(.*) +0.23/6 ..."
13664                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13665                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13666                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13667             }
13668             time = -1; sec = -1; deci = -1;
13669             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13670                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13671                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13672                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13673                 return text;
13674             }
13675
13676             if( score_lo < 0 || score_lo >= 100 ) {
13677                 return text;
13678             }
13679
13680             if(sec >= 0) time = 600*time + 10*sec; else
13681             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13682
13683             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13684
13685             /* [HGM] PV time: now locate end of PV info */
13686             while( *++sep >= '0' && *sep <= '9'); // strip depth
13687             if(time >= 0)
13688             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13689             if(sec >= 0)
13690             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13691             if(deci >= 0)
13692             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13693             while(*sep == ' ') sep++;
13694         }
13695
13696         if( depth <= 0 ) {
13697             return text;
13698         }
13699
13700         if( time < 0 ) {
13701             time = -1;
13702         }
13703
13704         pvInfoList[index-1].depth = depth;
13705         pvInfoList[index-1].score = score;
13706         pvInfoList[index-1].time  = 10*time; // centi-sec
13707         if(*sep == '}') *sep = 0; else *--sep = '{';
13708         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13709     }
13710     return sep;
13711 }
13712
13713 void
13714 SendToProgram(message, cps)
13715      char *message;
13716      ChessProgramState *cps;
13717 {
13718     int count, outCount, error;
13719     char buf[MSG_SIZ];
13720
13721     if (cps->pr == NULL) return;
13722     Attention(cps);
13723
13724     if (appData.debugMode) {
13725         TimeMark now;
13726         GetTimeMark(&now);
13727         fprintf(debugFP, "%ld >%-6s: %s",
13728                 SubtractTimeMarks(&now, &programStartTime),
13729                 cps->which, message);
13730     }
13731
13732     count = strlen(message);
13733     outCount = OutputToProcess(cps->pr, message, count, &error);
13734     if (outCount < count && !exiting
13735                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13736       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13737         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13738             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13739                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13740                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13741             } else {
13742                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13743             }
13744             gameInfo.resultDetails = StrSave(buf);
13745         }
13746         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13747     }
13748 }
13749
13750 void
13751 ReceiveFromProgram(isr, closure, message, count, error)
13752      InputSourceRef isr;
13753      VOIDSTAR closure;
13754      char *message;
13755      int count;
13756      int error;
13757 {
13758     char *end_str;
13759     char buf[MSG_SIZ];
13760     ChessProgramState *cps = (ChessProgramState *)closure;
13761
13762     if (isr != cps->isr) return; /* Killed intentionally */
13763     if (count <= 0) {
13764         if (count == 0) {
13765             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13766                     _(cps->which), cps->program);
13767         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13768                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13769                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13770                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13771                 } else {
13772                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13773                 }
13774                 gameInfo.resultDetails = StrSave(buf);
13775             }
13776             RemoveInputSource(cps->isr);
13777             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13778         } else {
13779             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13780                     _(cps->which), cps->program);
13781             RemoveInputSource(cps->isr);
13782
13783             /* [AS] Program is misbehaving badly... kill it */
13784             if( count == -2 ) {
13785                 DestroyChildProcess( cps->pr, 9 );
13786                 cps->pr = NoProc;
13787             }
13788
13789             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13790         }
13791         return;
13792     }
13793
13794     if ((end_str = strchr(message, '\r')) != NULL)
13795       *end_str = NULLCHAR;
13796     if ((end_str = strchr(message, '\n')) != NULL)
13797       *end_str = NULLCHAR;
13798
13799     if (appData.debugMode) {
13800         TimeMark now; int print = 1;
13801         char *quote = ""; char c; int i;
13802
13803         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13804                 char start = message[0];
13805                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13806                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13807                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13808                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13809                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13810                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13811                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13812                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13813                    sscanf(message, "hint: %c", &c)!=1 && 
13814                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13815                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13816                     print = (appData.engineComments >= 2);
13817                 }
13818                 message[0] = start; // restore original message
13819         }
13820         if(print) {
13821                 GetTimeMark(&now);
13822                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13823                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13824                         quote,
13825                         message);
13826         }
13827     }
13828
13829     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13830     if (appData.icsEngineAnalyze) {
13831         if (strstr(message, "whisper") != NULL ||
13832              strstr(message, "kibitz") != NULL ||
13833             strstr(message, "tellics") != NULL) return;
13834     }
13835
13836     HandleMachineMove(message, cps);
13837 }
13838
13839
13840 void
13841 SendTimeControl(cps, mps, tc, inc, sd, st)
13842      ChessProgramState *cps;
13843      int mps, inc, sd, st;
13844      long tc;
13845 {
13846     char buf[MSG_SIZ];
13847     int seconds;
13848
13849     if( timeControl_2 > 0 ) {
13850         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13851             tc = timeControl_2;
13852         }
13853     }
13854     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13855     inc /= cps->timeOdds;
13856     st  /= cps->timeOdds;
13857
13858     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13859
13860     if (st > 0) {
13861       /* Set exact time per move, normally using st command */
13862       if (cps->stKludge) {
13863         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13864         seconds = st % 60;
13865         if (seconds == 0) {
13866           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13867         } else {
13868           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13869         }
13870       } else {
13871         snprintf(buf, MSG_SIZ, "st %d\n", st);
13872       }
13873     } else {
13874       /* Set conventional or incremental time control, using level command */
13875       if (seconds == 0) {
13876         /* Note old gnuchess bug -- minutes:seconds used to not work.
13877            Fixed in later versions, but still avoid :seconds
13878            when seconds is 0. */
13879         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13880       } else {
13881         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13882                  seconds, inc/1000.);
13883       }
13884     }
13885     SendToProgram(buf, cps);
13886
13887     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13888     /* Orthogonally, limit search to given depth */
13889     if (sd > 0) {
13890       if (cps->sdKludge) {
13891         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13892       } else {
13893         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13894       }
13895       SendToProgram(buf, cps);
13896     }
13897
13898     if(cps->nps >= 0) { /* [HGM] nps */
13899         if(cps->supportsNPS == FALSE)
13900           cps->nps = -1; // don't use if engine explicitly says not supported!
13901         else {
13902           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13903           SendToProgram(buf, cps);
13904         }
13905     }
13906 }
13907
13908 ChessProgramState *WhitePlayer()
13909 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13910 {
13911     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13912        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13913         return &second;
13914     return &first;
13915 }
13916
13917 void
13918 SendTimeRemaining(cps, machineWhite)
13919      ChessProgramState *cps;
13920      int /*boolean*/ machineWhite;
13921 {
13922     char message[MSG_SIZ];
13923     long time, otime;
13924
13925     /* Note: this routine must be called when the clocks are stopped
13926        or when they have *just* been set or switched; otherwise
13927        it will be off by the time since the current tick started.
13928     */
13929     if (machineWhite) {
13930         time = whiteTimeRemaining / 10;
13931         otime = blackTimeRemaining / 10;
13932     } else {
13933         time = blackTimeRemaining / 10;
13934         otime = whiteTimeRemaining / 10;
13935     }
13936     /* [HGM] translate opponent's time by time-odds factor */
13937     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13938     if (appData.debugMode) {
13939         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13940     }
13941
13942     if (time <= 0) time = 1;
13943     if (otime <= 0) otime = 1;
13944
13945     snprintf(message, MSG_SIZ, "time %ld\n", time);
13946     SendToProgram(message, cps);
13947
13948     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13949     SendToProgram(message, cps);
13950 }
13951
13952 int
13953 BoolFeature(p, name, loc, cps)
13954      char **p;
13955      char *name;
13956      int *loc;
13957      ChessProgramState *cps;
13958 {
13959   char buf[MSG_SIZ];
13960   int len = strlen(name);
13961   int val;
13962
13963   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13964     (*p) += len + 1;
13965     sscanf(*p, "%d", &val);
13966     *loc = (val != 0);
13967     while (**p && **p != ' ')
13968       (*p)++;
13969     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13970     SendToProgram(buf, cps);
13971     return TRUE;
13972   }
13973   return FALSE;
13974 }
13975
13976 int
13977 IntFeature(p, name, loc, cps)
13978      char **p;
13979      char *name;
13980      int *loc;
13981      ChessProgramState *cps;
13982 {
13983   char buf[MSG_SIZ];
13984   int len = strlen(name);
13985   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13986     (*p) += len + 1;
13987     sscanf(*p, "%d", loc);
13988     while (**p && **p != ' ') (*p)++;
13989     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13990     SendToProgram(buf, cps);
13991     return TRUE;
13992   }
13993   return FALSE;
13994 }
13995
13996 int
13997 StringFeature(p, name, loc, cps)
13998      char **p;
13999      char *name;
14000      char loc[];
14001      ChessProgramState *cps;
14002 {
14003   char buf[MSG_SIZ];
14004   int len = strlen(name);
14005   if (strncmp((*p), name, len) == 0
14006       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14007     (*p) += len + 2;
14008     sscanf(*p, "%[^\"]", loc);
14009     while (**p && **p != '\"') (*p)++;
14010     if (**p == '\"') (*p)++;
14011     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14012     SendToProgram(buf, cps);
14013     return TRUE;
14014   }
14015   return FALSE;
14016 }
14017
14018 int
14019 ParseOption(Option *opt, ChessProgramState *cps)
14020 // [HGM] options: process the string that defines an engine option, and determine
14021 // name, type, default value, and allowed value range
14022 {
14023         char *p, *q, buf[MSG_SIZ];
14024         int n, min = (-1)<<31, max = 1<<31, def;
14025
14026         if(p = strstr(opt->name, " -spin ")) {
14027             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14028             if(max < min) max = min; // enforce consistency
14029             if(def < min) def = min;
14030             if(def > max) def = max;
14031             opt->value = def;
14032             opt->min = min;
14033             opt->max = max;
14034             opt->type = Spin;
14035         } else if((p = strstr(opt->name, " -slider "))) {
14036             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14037             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14038             if(max < min) max = min; // enforce consistency
14039             if(def < min) def = min;
14040             if(def > max) def = max;
14041             opt->value = def;
14042             opt->min = min;
14043             opt->max = max;
14044             opt->type = Spin; // Slider;
14045         } else if((p = strstr(opt->name, " -string "))) {
14046             opt->textValue = p+9;
14047             opt->type = TextBox;
14048         } else if((p = strstr(opt->name, " -file "))) {
14049             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14050             opt->textValue = p+7;
14051             opt->type = FileName; // FileName;
14052         } else if((p = strstr(opt->name, " -path "))) {
14053             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14054             opt->textValue = p+7;
14055             opt->type = PathName; // PathName;
14056         } else if(p = strstr(opt->name, " -check ")) {
14057             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14058             opt->value = (def != 0);
14059             opt->type = CheckBox;
14060         } else if(p = strstr(opt->name, " -combo ")) {
14061             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14062             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14063             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14064             opt->value = n = 0;
14065             while(q = StrStr(q, " /// ")) {
14066                 n++; *q = 0;    // count choices, and null-terminate each of them
14067                 q += 5;
14068                 if(*q == '*') { // remember default, which is marked with * prefix
14069                     q++;
14070                     opt->value = n;
14071                 }
14072                 cps->comboList[cps->comboCnt++] = q;
14073             }
14074             cps->comboList[cps->comboCnt++] = NULL;
14075             opt->max = n + 1;
14076             opt->type = ComboBox;
14077         } else if(p = strstr(opt->name, " -button")) {
14078             opt->type = Button;
14079         } else if(p = strstr(opt->name, " -save")) {
14080             opt->type = SaveButton;
14081         } else return FALSE;
14082         *p = 0; // terminate option name
14083         // now look if the command-line options define a setting for this engine option.
14084         if(cps->optionSettings && cps->optionSettings[0])
14085             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14086         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14087           snprintf(buf, MSG_SIZ, "option %s", p);
14088                 if(p = strstr(buf, ",")) *p = 0;
14089                 if(q = strchr(buf, '=')) switch(opt->type) {
14090                     case ComboBox:
14091                         for(n=0; n<opt->max; n++)
14092                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14093                         break;
14094                     case TextBox:
14095                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14096                         break;
14097                     case Spin:
14098                     case CheckBox:
14099                         opt->value = atoi(q+1);
14100                     default:
14101                         break;
14102                 }
14103                 strcat(buf, "\n");
14104                 SendToProgram(buf, cps);
14105         }
14106         return TRUE;
14107 }
14108
14109 void
14110 FeatureDone(cps, val)
14111      ChessProgramState* cps;
14112      int val;
14113 {
14114   DelayedEventCallback cb = GetDelayedEvent();
14115   if ((cb == InitBackEnd3 && cps == &first) ||
14116       (cb == SettingsMenuIfReady && cps == &second) ||
14117       (cb == TwoMachinesEventIfReady && cps == &second)) {
14118     CancelDelayedEvent();
14119     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14120   }
14121   cps->initDone = val;
14122 }
14123
14124 /* Parse feature command from engine */
14125 void
14126 ParseFeatures(args, cps)
14127      char* args;
14128      ChessProgramState *cps;
14129 {
14130   char *p = args;
14131   char *q;
14132   int val;
14133   char buf[MSG_SIZ];
14134
14135   for (;;) {
14136     while (*p == ' ') p++;
14137     if (*p == NULLCHAR) return;
14138
14139     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14140     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14141     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14142     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14143     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14144     if (BoolFeature(&p, "reuse", &val, cps)) {
14145       /* Engine can disable reuse, but can't enable it if user said no */
14146       if (!val) cps->reuse = FALSE;
14147       continue;
14148     }
14149     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14150     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14151       if (gameMode == TwoMachinesPlay) {
14152         DisplayTwoMachinesTitle();
14153       } else {
14154         DisplayTitle("");
14155       }
14156       continue;
14157     }
14158     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14159     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14160     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14161     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14162     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14163     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14164     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14165     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14166     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14167     if (IntFeature(&p, "done", &val, cps)) {
14168       FeatureDone(cps, val);
14169       continue;
14170     }
14171     /* Added by Tord: */
14172     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14173     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14174     /* End of additions by Tord */
14175
14176     /* [HGM] added features: */
14177     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14178     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14179     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14180     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14181     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14182     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14183     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14184         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14185           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14186             SendToProgram(buf, cps);
14187             continue;
14188         }
14189         if(cps->nrOptions >= MAX_OPTIONS) {
14190             cps->nrOptions--;
14191             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14192             DisplayError(buf, 0);
14193         }
14194         continue;
14195     }
14196     /* End of additions by HGM */
14197
14198     /* unknown feature: complain and skip */
14199     q = p;
14200     while (*q && *q != '=') q++;
14201     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14202     SendToProgram(buf, cps);
14203     p = q;
14204     if (*p == '=') {
14205       p++;
14206       if (*p == '\"') {
14207         p++;
14208         while (*p && *p != '\"') p++;
14209         if (*p == '\"') p++;
14210       } else {
14211         while (*p && *p != ' ') p++;
14212       }
14213     }
14214   }
14215
14216 }
14217
14218 void
14219 PeriodicUpdatesEvent(newState)
14220      int newState;
14221 {
14222     if (newState == appData.periodicUpdates)
14223       return;
14224
14225     appData.periodicUpdates=newState;
14226
14227     /* Display type changes, so update it now */
14228 //    DisplayAnalysis();
14229
14230     /* Get the ball rolling again... */
14231     if (newState) {
14232         AnalysisPeriodicEvent(1);
14233         StartAnalysisClock();
14234     }
14235 }
14236
14237 void
14238 PonderNextMoveEvent(newState)
14239      int newState;
14240 {
14241     if (newState == appData.ponderNextMove) return;
14242     if (gameMode == EditPosition) EditPositionDone(TRUE);
14243     if (newState) {
14244         SendToProgram("hard\n", &first);
14245         if (gameMode == TwoMachinesPlay) {
14246             SendToProgram("hard\n", &second);
14247         }
14248     } else {
14249         SendToProgram("easy\n", &first);
14250         thinkOutput[0] = NULLCHAR;
14251         if (gameMode == TwoMachinesPlay) {
14252             SendToProgram("easy\n", &second);
14253         }
14254     }
14255     appData.ponderNextMove = newState;
14256 }
14257
14258 void
14259 NewSettingEvent(option, feature, command, value)
14260      char *command;
14261      int option, value, *feature;
14262 {
14263     char buf[MSG_SIZ];
14264
14265     if (gameMode == EditPosition) EditPositionDone(TRUE);
14266     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14267     if(feature == NULL || *feature) SendToProgram(buf, &first);
14268     if (gameMode == TwoMachinesPlay) {
14269         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14270     }
14271 }
14272
14273 void
14274 ShowThinkingEvent()
14275 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14276 {
14277     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14278     int newState = appData.showThinking
14279         // [HGM] thinking: other features now need thinking output as well
14280         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14281
14282     if (oldState == newState) return;
14283     oldState = newState;
14284     if (gameMode == EditPosition) EditPositionDone(TRUE);
14285     if (oldState) {
14286         SendToProgram("post\n", &first);
14287         if (gameMode == TwoMachinesPlay) {
14288             SendToProgram("post\n", &second);
14289         }
14290     } else {
14291         SendToProgram("nopost\n", &first);
14292         thinkOutput[0] = NULLCHAR;
14293         if (gameMode == TwoMachinesPlay) {
14294             SendToProgram("nopost\n", &second);
14295         }
14296     }
14297 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14298 }
14299
14300 void
14301 AskQuestionEvent(title, question, replyPrefix, which)
14302      char *title; char *question; char *replyPrefix; char *which;
14303 {
14304   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14305   if (pr == NoProc) return;
14306   AskQuestion(title, question, replyPrefix, pr);
14307 }
14308
14309 void
14310 TypeInEvent(char firstChar)
14311 {
14312     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14313         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14314         gameMode == AnalyzeMode || gameMode == EditGame || \r
14315         gameMode == EditPosition || gameMode == IcsExamining ||\r
14316         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14317         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14318                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14319                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14320         gameMode == Training) PopUpMoveDialog(firstChar);
14321 }
14322
14323 void
14324 TypeInDoneEvent(char *move)
14325 {
14326         Board board;
14327         int n, fromX, fromY, toX, toY;
14328         char promoChar;
14329         ChessMove moveType;\r
14330
14331         // [HGM] FENedit\r
14332         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14333                 EditPositionPasteFEN(move);\r
14334                 return;\r
14335         }\r
14336         // [HGM] movenum: allow move number to be typed in any mode\r
14337         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14338           ToNrEvent(2*n-1);\r
14339           return;\r
14340         }\r
14341
14342       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14343         gameMode != Training) {\r
14344         DisplayMoveError(_("Displayed move is not current"));\r
14345       } else {\r
14346         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14347           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14348         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14349         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14350           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14351           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14352         } else {\r
14353           DisplayMoveError(_("Could not parse move"));\r
14354         }
14355       }\r
14356 }\r
14357
14358 void
14359 DisplayMove(moveNumber)
14360      int moveNumber;
14361 {
14362     char message[MSG_SIZ];
14363     char res[MSG_SIZ];
14364     char cpThinkOutput[MSG_SIZ];
14365
14366     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14367
14368     if (moveNumber == forwardMostMove - 1 ||
14369         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14370
14371         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14372
14373         if (strchr(cpThinkOutput, '\n')) {
14374             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14375         }
14376     } else {
14377         *cpThinkOutput = NULLCHAR;
14378     }
14379
14380     /* [AS] Hide thinking from human user */
14381     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14382         *cpThinkOutput = NULLCHAR;
14383         if( thinkOutput[0] != NULLCHAR ) {
14384             int i;
14385
14386             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14387                 cpThinkOutput[i] = '.';
14388             }
14389             cpThinkOutput[i] = NULLCHAR;
14390             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14391         }
14392     }
14393
14394     if (moveNumber == forwardMostMove - 1 &&
14395         gameInfo.resultDetails != NULL) {
14396         if (gameInfo.resultDetails[0] == NULLCHAR) {
14397           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14398         } else {
14399           snprintf(res, MSG_SIZ, " {%s} %s",
14400                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14401         }
14402     } else {
14403         res[0] = NULLCHAR;
14404     }
14405
14406     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14407         DisplayMessage(res, cpThinkOutput);
14408     } else {
14409       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14410                 WhiteOnMove(moveNumber) ? " " : ".. ",
14411                 parseList[moveNumber], res);
14412         DisplayMessage(message, cpThinkOutput);
14413     }
14414 }
14415
14416 void
14417 DisplayComment(moveNumber, text)
14418      int moveNumber;
14419      char *text;
14420 {
14421     char title[MSG_SIZ];
14422     char buf[8000]; // comment can be long!
14423     int score, depth;
14424
14425     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14426       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14427     } else {
14428       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14429               WhiteOnMove(moveNumber) ? " " : ".. ",
14430               parseList[moveNumber]);
14431     }
14432     // [HGM] PV info: display PV info together with (or as) comment
14433     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14434       if(text == NULL) text = "";
14435       score = pvInfoList[moveNumber].score;
14436       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14437               depth, (pvInfoList[moveNumber].time+50)/100, text);
14438       text = buf;
14439     }
14440     if (text != NULL && (appData.autoDisplayComment || commentUp))
14441         CommentPopUp(title, text);
14442 }
14443
14444 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14445  * might be busy thinking or pondering.  It can be omitted if your
14446  * gnuchess is configured to stop thinking immediately on any user
14447  * input.  However, that gnuchess feature depends on the FIONREAD
14448  * ioctl, which does not work properly on some flavors of Unix.
14449  */
14450 void
14451 Attention(cps)
14452      ChessProgramState *cps;
14453 {
14454 #if ATTENTION
14455     if (!cps->useSigint) return;
14456     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14457     switch (gameMode) {
14458       case MachinePlaysWhite:
14459       case MachinePlaysBlack:
14460       case TwoMachinesPlay:
14461       case IcsPlayingWhite:
14462       case IcsPlayingBlack:
14463       case AnalyzeMode:
14464       case AnalyzeFile:
14465         /* Skip if we know it isn't thinking */
14466         if (!cps->maybeThinking) return;
14467         if (appData.debugMode)
14468           fprintf(debugFP, "Interrupting %s\n", cps->which);
14469         InterruptChildProcess(cps->pr);
14470         cps->maybeThinking = FALSE;
14471         break;
14472       default:
14473         break;
14474     }
14475 #endif /*ATTENTION*/
14476 }
14477
14478 int
14479 CheckFlags()
14480 {
14481     if (whiteTimeRemaining <= 0) {
14482         if (!whiteFlag) {
14483             whiteFlag = TRUE;
14484             if (appData.icsActive) {
14485                 if (appData.autoCallFlag &&
14486                     gameMode == IcsPlayingBlack && !blackFlag) {
14487                   SendToICS(ics_prefix);
14488                   SendToICS("flag\n");
14489                 }
14490             } else {
14491                 if (blackFlag) {
14492                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14493                 } else {
14494                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14495                     if (appData.autoCallFlag) {
14496                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14497                         return TRUE;
14498                     }
14499                 }
14500             }
14501         }
14502     }
14503     if (blackTimeRemaining <= 0) {
14504         if (!blackFlag) {
14505             blackFlag = TRUE;
14506             if (appData.icsActive) {
14507                 if (appData.autoCallFlag &&
14508                     gameMode == IcsPlayingWhite && !whiteFlag) {
14509                   SendToICS(ics_prefix);
14510                   SendToICS("flag\n");
14511                 }
14512             } else {
14513                 if (whiteFlag) {
14514                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14515                 } else {
14516                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14517                     if (appData.autoCallFlag) {
14518                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14519                         return TRUE;
14520                     }
14521                 }
14522             }
14523         }
14524     }
14525     return FALSE;
14526 }
14527
14528 void
14529 CheckTimeControl()
14530 {
14531     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14532         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14533
14534     /*
14535      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14536      */
14537     if ( !WhiteOnMove(forwardMostMove) ) {
14538         /* White made time control */
14539         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14540         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14541         /* [HGM] time odds: correct new time quota for time odds! */
14542                                             / WhitePlayer()->timeOdds;
14543         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14544     } else {
14545         lastBlack -= blackTimeRemaining;
14546         /* Black made time control */
14547         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14548                                             / WhitePlayer()->other->timeOdds;
14549         lastWhite = whiteTimeRemaining;
14550     }
14551 }
14552
14553 void
14554 DisplayBothClocks()
14555 {
14556     int wom = gameMode == EditPosition ?
14557       !blackPlaysFirst : WhiteOnMove(currentMove);
14558     DisplayWhiteClock(whiteTimeRemaining, wom);
14559     DisplayBlackClock(blackTimeRemaining, !wom);
14560 }
14561
14562
14563 /* Timekeeping seems to be a portability nightmare.  I think everyone
14564    has ftime(), but I'm really not sure, so I'm including some ifdefs
14565    to use other calls if you don't.  Clocks will be less accurate if
14566    you have neither ftime nor gettimeofday.
14567 */
14568
14569 /* VS 2008 requires the #include outside of the function */
14570 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14571 #include <sys/timeb.h>
14572 #endif
14573
14574 /* Get the current time as a TimeMark */
14575 void
14576 GetTimeMark(tm)
14577      TimeMark *tm;
14578 {
14579 #if HAVE_GETTIMEOFDAY
14580
14581     struct timeval timeVal;
14582     struct timezone timeZone;
14583
14584     gettimeofday(&timeVal, &timeZone);
14585     tm->sec = (long) timeVal.tv_sec;
14586     tm->ms = (int) (timeVal.tv_usec / 1000L);
14587
14588 #else /*!HAVE_GETTIMEOFDAY*/
14589 #if HAVE_FTIME
14590
14591 // include <sys/timeb.h> / moved to just above start of function
14592     struct timeb timeB;
14593
14594     ftime(&timeB);
14595     tm->sec = (long) timeB.time;
14596     tm->ms = (int) timeB.millitm;
14597
14598 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14599     tm->sec = (long) time(NULL);
14600     tm->ms = 0;
14601 #endif
14602 #endif
14603 }
14604
14605 /* Return the difference in milliseconds between two
14606    time marks.  We assume the difference will fit in a long!
14607 */
14608 long
14609 SubtractTimeMarks(tm2, tm1)
14610      TimeMark *tm2, *tm1;
14611 {
14612     return 1000L*(tm2->sec - tm1->sec) +
14613            (long) (tm2->ms - tm1->ms);
14614 }
14615
14616
14617 /*
14618  * Code to manage the game clocks.
14619  *
14620  * In tournament play, black starts the clock and then white makes a move.
14621  * We give the human user a slight advantage if he is playing white---the
14622  * clocks don't run until he makes his first move, so it takes zero time.
14623  * Also, we don't account for network lag, so we could get out of sync
14624  * with GNU Chess's clock -- but then, referees are always right.
14625  */
14626
14627 static TimeMark tickStartTM;
14628 static long intendedTickLength;
14629
14630 long
14631 NextTickLength(timeRemaining)
14632      long timeRemaining;
14633 {
14634     long nominalTickLength, nextTickLength;
14635
14636     if (timeRemaining > 0L && timeRemaining <= 10000L)
14637       nominalTickLength = 100L;
14638     else
14639       nominalTickLength = 1000L;
14640     nextTickLength = timeRemaining % nominalTickLength;
14641     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14642
14643     return nextTickLength;
14644 }
14645
14646 /* Adjust clock one minute up or down */
14647 void
14648 AdjustClock(Boolean which, int dir)
14649 {
14650     if(which) blackTimeRemaining += 60000*dir;
14651     else      whiteTimeRemaining += 60000*dir;
14652     DisplayBothClocks();
14653 }
14654
14655 /* Stop clocks and reset to a fresh time control */
14656 void
14657 ResetClocks()
14658 {
14659     (void) StopClockTimer();
14660     if (appData.icsActive) {
14661         whiteTimeRemaining = blackTimeRemaining = 0;
14662     } else if (searchTime) {
14663         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14664         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14665     } else { /* [HGM] correct new time quote for time odds */
14666         whiteTC = blackTC = fullTimeControlString;
14667         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14668         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14669     }
14670     if (whiteFlag || blackFlag) {
14671         DisplayTitle("");
14672         whiteFlag = blackFlag = FALSE;
14673     }
14674     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14675     DisplayBothClocks();
14676 }
14677
14678 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14679
14680 /* Decrement running clock by amount of time that has passed */
14681 void
14682 DecrementClocks()
14683 {
14684     long timeRemaining;
14685     long lastTickLength, fudge;
14686     TimeMark now;
14687
14688     if (!appData.clockMode) return;
14689     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14690
14691     GetTimeMark(&now);
14692
14693     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14694
14695     /* Fudge if we woke up a little too soon */
14696     fudge = intendedTickLength - lastTickLength;
14697     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14698
14699     if (WhiteOnMove(forwardMostMove)) {
14700         if(whiteNPS >= 0) lastTickLength = 0;
14701         timeRemaining = whiteTimeRemaining -= lastTickLength;
14702         if(timeRemaining < 0 && !appData.icsActive) {
14703             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14704             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14705                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14706                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14707             }
14708         }
14709         DisplayWhiteClock(whiteTimeRemaining - fudge,
14710                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14711     } else {
14712         if(blackNPS >= 0) lastTickLength = 0;
14713         timeRemaining = blackTimeRemaining -= lastTickLength;
14714         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14715             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14716             if(suddenDeath) {
14717                 blackStartMove = forwardMostMove;
14718                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14719             }
14720         }
14721         DisplayBlackClock(blackTimeRemaining - fudge,
14722                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14723     }
14724     if (CheckFlags()) return;
14725
14726     tickStartTM = now;
14727     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14728     StartClockTimer(intendedTickLength);
14729
14730     /* if the time remaining has fallen below the alarm threshold, sound the
14731      * alarm. if the alarm has sounded and (due to a takeback or time control
14732      * with increment) the time remaining has increased to a level above the
14733      * threshold, reset the alarm so it can sound again.
14734      */
14735
14736     if (appData.icsActive && appData.icsAlarm) {
14737
14738         /* make sure we are dealing with the user's clock */
14739         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14740                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14741            )) return;
14742
14743         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14744             alarmSounded = FALSE;
14745         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14746             PlayAlarmSound();
14747             alarmSounded = TRUE;
14748         }
14749     }
14750 }
14751
14752
14753 /* A player has just moved, so stop the previously running
14754    clock and (if in clock mode) start the other one.
14755    We redisplay both clocks in case we're in ICS mode, because
14756    ICS gives us an update to both clocks after every move.
14757    Note that this routine is called *after* forwardMostMove
14758    is updated, so the last fractional tick must be subtracted
14759    from the color that is *not* on move now.
14760 */
14761 void
14762 SwitchClocks(int newMoveNr)
14763 {
14764     long lastTickLength;
14765     TimeMark now;
14766     int flagged = FALSE;
14767
14768     GetTimeMark(&now);
14769
14770     if (StopClockTimer() && appData.clockMode) {
14771         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14772         if (!WhiteOnMove(forwardMostMove)) {
14773             if(blackNPS >= 0) lastTickLength = 0;
14774             blackTimeRemaining -= lastTickLength;
14775            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14776 //         if(pvInfoList[forwardMostMove].time == -1)
14777                  pvInfoList[forwardMostMove].time =               // use GUI time
14778                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14779         } else {
14780            if(whiteNPS >= 0) lastTickLength = 0;
14781            whiteTimeRemaining -= lastTickLength;
14782            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14783 //         if(pvInfoList[forwardMostMove].time == -1)
14784                  pvInfoList[forwardMostMove].time =
14785                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14786         }
14787         flagged = CheckFlags();
14788     }
14789     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14790     CheckTimeControl();
14791
14792     if (flagged || !appData.clockMode) return;
14793
14794     switch (gameMode) {
14795       case MachinePlaysBlack:
14796       case MachinePlaysWhite:
14797       case BeginningOfGame:
14798         if (pausing) return;
14799         break;
14800
14801       case EditGame:
14802       case PlayFromGameFile:
14803       case IcsExamining:
14804         return;
14805
14806       default:
14807         break;
14808     }
14809
14810     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14811         if(WhiteOnMove(forwardMostMove))
14812              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14813         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14814     }
14815
14816     tickStartTM = now;
14817     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14818       whiteTimeRemaining : blackTimeRemaining);
14819     StartClockTimer(intendedTickLength);
14820 }
14821
14822
14823 /* Stop both clocks */
14824 void
14825 StopClocks()
14826 {
14827     long lastTickLength;
14828     TimeMark now;
14829
14830     if (!StopClockTimer()) return;
14831     if (!appData.clockMode) return;
14832
14833     GetTimeMark(&now);
14834
14835     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14836     if (WhiteOnMove(forwardMostMove)) {
14837         if(whiteNPS >= 0) lastTickLength = 0;
14838         whiteTimeRemaining -= lastTickLength;
14839         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14840     } else {
14841         if(blackNPS >= 0) lastTickLength = 0;
14842         blackTimeRemaining -= lastTickLength;
14843         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14844     }
14845     CheckFlags();
14846 }
14847
14848 /* Start clock of player on move.  Time may have been reset, so
14849    if clock is already running, stop and restart it. */
14850 void
14851 StartClocks()
14852 {
14853     (void) StopClockTimer(); /* in case it was running already */
14854     DisplayBothClocks();
14855     if (CheckFlags()) return;
14856
14857     if (!appData.clockMode) return;
14858     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14859
14860     GetTimeMark(&tickStartTM);
14861     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14862       whiteTimeRemaining : blackTimeRemaining);
14863
14864    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14865     whiteNPS = blackNPS = -1;
14866     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14867        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14868         whiteNPS = first.nps;
14869     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14870        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14871         blackNPS = first.nps;
14872     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14873         whiteNPS = second.nps;
14874     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14875         blackNPS = second.nps;
14876     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14877
14878     StartClockTimer(intendedTickLength);
14879 }
14880
14881 char *
14882 TimeString(ms)
14883      long ms;
14884 {
14885     long second, minute, hour, day;
14886     char *sign = "";
14887     static char buf[32];
14888
14889     if (ms > 0 && ms <= 9900) {
14890       /* convert milliseconds to tenths, rounding up */
14891       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14892
14893       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14894       return buf;
14895     }
14896
14897     /* convert milliseconds to seconds, rounding up */
14898     /* use floating point to avoid strangeness of integer division
14899        with negative dividends on many machines */
14900     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14901
14902     if (second < 0) {
14903         sign = "-";
14904         second = -second;
14905     }
14906
14907     day = second / (60 * 60 * 24);
14908     second = second % (60 * 60 * 24);
14909     hour = second / (60 * 60);
14910     second = second % (60 * 60);
14911     minute = second / 60;
14912     second = second % 60;
14913
14914     if (day > 0)
14915       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14916               sign, day, hour, minute, second);
14917     else if (hour > 0)
14918       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14919     else
14920       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14921
14922     return buf;
14923 }
14924
14925
14926 /*
14927  * This is necessary because some C libraries aren't ANSI C compliant yet.
14928  */
14929 char *
14930 StrStr(string, match)
14931      char *string, *match;
14932 {
14933     int i, length;
14934
14935     length = strlen(match);
14936
14937     for (i = strlen(string) - length; i >= 0; i--, string++)
14938       if (!strncmp(match, string, length))
14939         return string;
14940
14941     return NULL;
14942 }
14943
14944 char *
14945 StrCaseStr(string, match)
14946      char *string, *match;
14947 {
14948     int i, j, length;
14949
14950     length = strlen(match);
14951
14952     for (i = strlen(string) - length; i >= 0; i--, string++) {
14953         for (j = 0; j < length; j++) {
14954             if (ToLower(match[j]) != ToLower(string[j]))
14955               break;
14956         }
14957         if (j == length) return string;
14958     }
14959
14960     return NULL;
14961 }
14962
14963 #ifndef _amigados
14964 int
14965 StrCaseCmp(s1, s2)
14966      char *s1, *s2;
14967 {
14968     char c1, c2;
14969
14970     for (;;) {
14971         c1 = ToLower(*s1++);
14972         c2 = ToLower(*s2++);
14973         if (c1 > c2) return 1;
14974         if (c1 < c2) return -1;
14975         if (c1 == NULLCHAR) return 0;
14976     }
14977 }
14978
14979
14980 int
14981 ToLower(c)
14982      int c;
14983 {
14984     return isupper(c) ? tolower(c) : c;
14985 }
14986
14987
14988 int
14989 ToUpper(c)
14990      int c;
14991 {
14992     return islower(c) ? toupper(c) : c;
14993 }
14994 #endif /* !_amigados    */
14995
14996 char *
14997 StrSave(s)
14998      char *s;
14999 {
15000   char *ret;
15001
15002   if ((ret = (char *) malloc(strlen(s) + 1)))
15003     {
15004       safeStrCpy(ret, s, strlen(s)+1);
15005     }
15006   return ret;
15007 }
15008
15009 char *
15010 StrSavePtr(s, savePtr)
15011      char *s, **savePtr;
15012 {
15013     if (*savePtr) {
15014         free(*savePtr);
15015     }
15016     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15017       safeStrCpy(*savePtr, s, strlen(s)+1);
15018     }
15019     return(*savePtr);
15020 }
15021
15022 char *
15023 PGNDate()
15024 {
15025     time_t clock;
15026     struct tm *tm;
15027     char buf[MSG_SIZ];
15028
15029     clock = time((time_t *)NULL);
15030     tm = localtime(&clock);
15031     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15032             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15033     return StrSave(buf);
15034 }
15035
15036
15037 char *
15038 PositionToFEN(move, overrideCastling)
15039      int move;
15040      char *overrideCastling;
15041 {
15042     int i, j, fromX, fromY, toX, toY;
15043     int whiteToPlay;
15044     char buf[128];
15045     char *p, *q;
15046     int emptycount;
15047     ChessSquare piece;
15048
15049     whiteToPlay = (gameMode == EditPosition) ?
15050       !blackPlaysFirst : (move % 2 == 0);
15051     p = buf;
15052
15053     /* Piece placement data */
15054     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15055         emptycount = 0;
15056         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15057             if (boards[move][i][j] == EmptySquare) {
15058                 emptycount++;
15059             } else { ChessSquare piece = boards[move][i][j];
15060                 if (emptycount > 0) {
15061                     if(emptycount<10) /* [HGM] can be >= 10 */
15062                         *p++ = '0' + emptycount;
15063                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15064                     emptycount = 0;
15065                 }
15066                 if(PieceToChar(piece) == '+') {
15067                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15068                     *p++ = '+';
15069                     piece = (ChessSquare)(DEMOTED piece);
15070                 }
15071                 *p++ = PieceToChar(piece);
15072                 if(p[-1] == '~') {
15073                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15074                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15075                     *p++ = '~';
15076                 }
15077             }
15078         }
15079         if (emptycount > 0) {
15080             if(emptycount<10) /* [HGM] can be >= 10 */
15081                 *p++ = '0' + emptycount;
15082             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15083             emptycount = 0;
15084         }
15085         *p++ = '/';
15086     }
15087     *(p - 1) = ' ';
15088
15089     /* [HGM] print Crazyhouse or Shogi holdings */
15090     if( gameInfo.holdingsWidth ) {
15091         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15092         q = p;
15093         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15094             piece = boards[move][i][BOARD_WIDTH-1];
15095             if( piece != EmptySquare )
15096               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15097                   *p++ = PieceToChar(piece);
15098         }
15099         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15100             piece = boards[move][BOARD_HEIGHT-i-1][0];
15101             if( piece != EmptySquare )
15102               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15103                   *p++ = PieceToChar(piece);
15104         }
15105
15106         if( q == p ) *p++ = '-';
15107         *p++ = ']';
15108         *p++ = ' ';
15109     }
15110
15111     /* Active color */
15112     *p++ = whiteToPlay ? 'w' : 'b';
15113     *p++ = ' ';
15114
15115   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15116     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15117   } else {
15118   if(nrCastlingRights) {
15119      q = p;
15120      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15121        /* [HGM] write directly from rights */
15122            if(boards[move][CASTLING][2] != NoRights &&
15123               boards[move][CASTLING][0] != NoRights   )
15124                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15125            if(boards[move][CASTLING][2] != NoRights &&
15126               boards[move][CASTLING][1] != NoRights   )
15127                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15128            if(boards[move][CASTLING][5] != NoRights &&
15129               boards[move][CASTLING][3] != NoRights   )
15130                 *p++ = boards[move][CASTLING][3] + AAA;
15131            if(boards[move][CASTLING][5] != NoRights &&
15132               boards[move][CASTLING][4] != NoRights   )
15133                 *p++ = boards[move][CASTLING][4] + AAA;
15134      } else {
15135
15136         /* [HGM] write true castling rights */
15137         if( nrCastlingRights == 6 ) {
15138             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15139                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15140             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15141                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15142             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15143                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15144             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15145                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15146         }
15147      }
15148      if (q == p) *p++ = '-'; /* No castling rights */
15149      *p++ = ' ';
15150   }
15151
15152   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15153      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15154     /* En passant target square */
15155     if (move > backwardMostMove) {
15156         fromX = moveList[move - 1][0] - AAA;
15157         fromY = moveList[move - 1][1] - ONE;
15158         toX = moveList[move - 1][2] - AAA;
15159         toY = moveList[move - 1][3] - ONE;
15160         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15161             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15162             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15163             fromX == toX) {
15164             /* 2-square pawn move just happened */
15165             *p++ = toX + AAA;
15166             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15167         } else {
15168             *p++ = '-';
15169         }
15170     } else if(move == backwardMostMove) {
15171         // [HGM] perhaps we should always do it like this, and forget the above?
15172         if((signed char)boards[move][EP_STATUS] >= 0) {
15173             *p++ = boards[move][EP_STATUS] + AAA;
15174             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15175         } else {
15176             *p++ = '-';
15177         }
15178     } else {
15179         *p++ = '-';
15180     }
15181     *p++ = ' ';
15182   }
15183   }
15184
15185     /* [HGM] find reversible plies */
15186     {   int i = 0, j=move;
15187
15188         if (appData.debugMode) { int k;
15189             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15190             for(k=backwardMostMove; k<=forwardMostMove; k++)
15191                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15192
15193         }
15194
15195         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15196         if( j == backwardMostMove ) i += initialRulePlies;
15197         sprintf(p, "%d ", i);
15198         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15199     }
15200     /* Fullmove number */
15201     sprintf(p, "%d", (move / 2) + 1);
15202
15203     return StrSave(buf);
15204 }
15205
15206 Boolean
15207 ParseFEN(board, blackPlaysFirst, fen)
15208     Board board;
15209      int *blackPlaysFirst;
15210      char *fen;
15211 {
15212     int i, j;
15213     char *p, c;
15214     int emptycount;
15215     ChessSquare piece;
15216
15217     p = fen;
15218
15219     /* [HGM] by default clear Crazyhouse holdings, if present */
15220     if(gameInfo.holdingsWidth) {
15221        for(i=0; i<BOARD_HEIGHT; i++) {
15222            board[i][0]             = EmptySquare; /* black holdings */
15223            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15224            board[i][1]             = (ChessSquare) 0; /* black counts */
15225            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15226        }
15227     }
15228
15229     /* Piece placement data */
15230     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15231         j = 0;
15232         for (;;) {
15233             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15234                 if (*p == '/') p++;
15235                 emptycount = gameInfo.boardWidth - j;
15236                 while (emptycount--)
15237                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15238                 break;
15239 #if(BOARD_FILES >= 10)
15240             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15241                 p++; emptycount=10;
15242                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15243                 while (emptycount--)
15244                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15245 #endif
15246             } else if (isdigit(*p)) {
15247                 emptycount = *p++ - '0';
15248                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15249                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15250                 while (emptycount--)
15251                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15252             } else if (*p == '+' || isalpha(*p)) {
15253                 if (j >= gameInfo.boardWidth) return FALSE;
15254                 if(*p=='+') {
15255                     piece = CharToPiece(*++p);
15256                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15257                     piece = (ChessSquare) (PROMOTED piece ); p++;
15258                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15259                 } else piece = CharToPiece(*p++);
15260
15261                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15262                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15263                     piece = (ChessSquare) (PROMOTED piece);
15264                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15265                     p++;
15266                 }
15267                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15268             } else {
15269                 return FALSE;
15270             }
15271         }
15272     }
15273     while (*p == '/' || *p == ' ') p++;
15274
15275     /* [HGM] look for Crazyhouse holdings here */
15276     while(*p==' ') p++;
15277     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15278         if(*p == '[') p++;
15279         if(*p == '-' ) p++; /* empty holdings */ else {
15280             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15281             /* if we would allow FEN reading to set board size, we would   */
15282             /* have to add holdings and shift the board read so far here   */
15283             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15284                 p++;
15285                 if((int) piece >= (int) BlackPawn ) {
15286                     i = (int)piece - (int)BlackPawn;
15287                     i = PieceToNumber((ChessSquare)i);
15288                     if( i >= gameInfo.holdingsSize ) return FALSE;
15289                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15290                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15291                 } else {
15292                     i = (int)piece - (int)WhitePawn;
15293                     i = PieceToNumber((ChessSquare)i);
15294                     if( i >= gameInfo.holdingsSize ) return FALSE;
15295                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15296                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15297                 }
15298             }
15299         }
15300         if(*p == ']') p++;
15301     }
15302
15303     while(*p == ' ') p++;
15304
15305     /* Active color */
15306     c = *p++;
15307     if(appData.colorNickNames) {
15308       if( c == appData.colorNickNames[0] ) c = 'w'; else
15309       if( c == appData.colorNickNames[1] ) c = 'b';
15310     }
15311     switch (c) {
15312       case 'w':
15313         *blackPlaysFirst = FALSE;
15314         break;
15315       case 'b':
15316         *blackPlaysFirst = TRUE;
15317         break;
15318       default:
15319         return FALSE;
15320     }
15321
15322     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15323     /* return the extra info in global variiables             */
15324
15325     /* set defaults in case FEN is incomplete */
15326     board[EP_STATUS] = EP_UNKNOWN;
15327     for(i=0; i<nrCastlingRights; i++ ) {
15328         board[CASTLING][i] =
15329             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15330     }   /* assume possible unless obviously impossible */
15331     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15332     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15333     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15334                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15335     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15336     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15337     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15338                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15339     FENrulePlies = 0;
15340
15341     while(*p==' ') p++;
15342     if(nrCastlingRights) {
15343       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15344           /* castling indicator present, so default becomes no castlings */
15345           for(i=0; i<nrCastlingRights; i++ ) {
15346                  board[CASTLING][i] = NoRights;
15347           }
15348       }
15349       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15350              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15351              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15352              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15353         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15354
15355         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15356             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15357             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15358         }
15359         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15360             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15361         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15362                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15363         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15364                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15365         switch(c) {
15366           case'K':
15367               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15368               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15369               board[CASTLING][2] = whiteKingFile;
15370               break;
15371           case'Q':
15372               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15373               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15374               board[CASTLING][2] = whiteKingFile;
15375               break;
15376           case'k':
15377               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15378               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15379               board[CASTLING][5] = blackKingFile;
15380               break;
15381           case'q':
15382               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15383               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15384               board[CASTLING][5] = blackKingFile;
15385           case '-':
15386               break;
15387           default: /* FRC castlings */
15388               if(c >= 'a') { /* black rights */
15389                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15390                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15391                   if(i == BOARD_RGHT) break;
15392                   board[CASTLING][5] = i;
15393                   c -= AAA;
15394                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15395                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15396                   if(c > i)
15397                       board[CASTLING][3] = c;
15398                   else
15399                       board[CASTLING][4] = c;
15400               } else { /* white rights */
15401                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15402                     if(board[0][i] == WhiteKing) break;
15403                   if(i == BOARD_RGHT) break;
15404                   board[CASTLING][2] = i;
15405                   c -= AAA - 'a' + 'A';
15406                   if(board[0][c] >= WhiteKing) break;
15407                   if(c > i)
15408                       board[CASTLING][0] = c;
15409                   else
15410                       board[CASTLING][1] = c;
15411               }
15412         }
15413       }
15414       for(i=0; i<nrCastlingRights; i++)
15415         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15416     if (appData.debugMode) {
15417         fprintf(debugFP, "FEN castling rights:");
15418         for(i=0; i<nrCastlingRights; i++)
15419         fprintf(debugFP, " %d", board[CASTLING][i]);
15420         fprintf(debugFP, "\n");
15421     }
15422
15423       while(*p==' ') p++;
15424     }
15425
15426     /* read e.p. field in games that know e.p. capture */
15427     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15428        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15429       if(*p=='-') {
15430         p++; board[EP_STATUS] = EP_NONE;
15431       } else {
15432          char c = *p++ - AAA;
15433
15434          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15435          if(*p >= '0' && *p <='9') p++;
15436          board[EP_STATUS] = c;
15437       }
15438     }
15439
15440
15441     if(sscanf(p, "%d", &i) == 1) {
15442         FENrulePlies = i; /* 50-move ply counter */
15443         /* (The move number is still ignored)    */
15444     }
15445
15446     return TRUE;
15447 }
15448
15449 void
15450 EditPositionPasteFEN(char *fen)
15451 {
15452   if (fen != NULL) {
15453     Board initial_position;
15454
15455     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15456       DisplayError(_("Bad FEN position in clipboard"), 0);
15457       return ;
15458     } else {
15459       int savedBlackPlaysFirst = blackPlaysFirst;
15460       EditPositionEvent();
15461       blackPlaysFirst = savedBlackPlaysFirst;
15462       CopyBoard(boards[0], initial_position);
15463       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15464       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15465       DisplayBothClocks();
15466       DrawPosition(FALSE, boards[currentMove]);
15467     }
15468   }
15469 }
15470
15471 static char cseq[12] = "\\   ";
15472
15473 Boolean set_cont_sequence(char *new_seq)
15474 {
15475     int len;
15476     Boolean ret;
15477
15478     // handle bad attempts to set the sequence
15479         if (!new_seq)
15480                 return 0; // acceptable error - no debug
15481
15482     len = strlen(new_seq);
15483     ret = (len > 0) && (len < sizeof(cseq));
15484     if (ret)
15485       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15486     else if (appData.debugMode)
15487       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15488     return ret;
15489 }
15490
15491 /*
15492     reformat a source message so words don't cross the width boundary.  internal
15493     newlines are not removed.  returns the wrapped size (no null character unless
15494     included in source message).  If dest is NULL, only calculate the size required
15495     for the dest buffer.  lp argument indicats line position upon entry, and it's
15496     passed back upon exit.
15497 */
15498 int wrap(char *dest, char *src, int count, int width, int *lp)
15499 {
15500     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15501
15502     cseq_len = strlen(cseq);
15503     old_line = line = *lp;
15504     ansi = len = clen = 0;
15505
15506     for (i=0; i < count; i++)
15507     {
15508         if (src[i] == '\033')
15509             ansi = 1;
15510
15511         // if we hit the width, back up
15512         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15513         {
15514             // store i & len in case the word is too long
15515             old_i = i, old_len = len;
15516
15517             // find the end of the last word
15518             while (i && src[i] != ' ' && src[i] != '\n')
15519             {
15520                 i--;
15521                 len--;
15522             }
15523
15524             // word too long?  restore i & len before splitting it
15525             if ((old_i-i+clen) >= width)
15526             {
15527                 i = old_i;
15528                 len = old_len;
15529             }
15530
15531             // extra space?
15532             if (i && src[i-1] == ' ')
15533                 len--;
15534
15535             if (src[i] != ' ' && src[i] != '\n')
15536             {
15537                 i--;
15538                 if (len)
15539                     len--;
15540             }
15541
15542             // now append the newline and continuation sequence
15543             if (dest)
15544                 dest[len] = '\n';
15545             len++;
15546             if (dest)
15547                 strncpy(dest+len, cseq, cseq_len);
15548             len += cseq_len;
15549             line = cseq_len;
15550             clen = cseq_len;
15551             continue;
15552         }
15553
15554         if (dest)
15555             dest[len] = src[i];
15556         len++;
15557         if (!ansi)
15558             line++;
15559         if (src[i] == '\n')
15560             line = 0;
15561         if (src[i] == 'm')
15562             ansi = 0;
15563     }
15564     if (dest && appData.debugMode)
15565     {
15566         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15567             count, width, line, len, *lp);
15568         show_bytes(debugFP, src, count);
15569         fprintf(debugFP, "\ndest: ");
15570         show_bytes(debugFP, dest, len);
15571         fprintf(debugFP, "\n");
15572     }
15573     *lp = dest ? line : old_line;
15574
15575     return len;
15576 }
15577
15578 // [HGM] vari: routines for shelving variations
15579
15580 void
15581 PushTail(int firstMove, int lastMove)
15582 {
15583         int i, j, nrMoves = lastMove - firstMove;
15584
15585         if(appData.icsActive) { // only in local mode
15586                 forwardMostMove = currentMove; // mimic old ICS behavior
15587                 return;
15588         }
15589         if(storedGames >= MAX_VARIATIONS-1) return;
15590
15591         // push current tail of game on stack
15592         savedResult[storedGames] = gameInfo.result;
15593         savedDetails[storedGames] = gameInfo.resultDetails;
15594         gameInfo.resultDetails = NULL;
15595         savedFirst[storedGames] = firstMove;
15596         savedLast [storedGames] = lastMove;
15597         savedFramePtr[storedGames] = framePtr;
15598         framePtr -= nrMoves; // reserve space for the boards
15599         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15600             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15601             for(j=0; j<MOVE_LEN; j++)
15602                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15603             for(j=0; j<2*MOVE_LEN; j++)
15604                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15605             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15606             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15607             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15608             pvInfoList[firstMove+i-1].depth = 0;
15609             commentList[framePtr+i] = commentList[firstMove+i];
15610             commentList[firstMove+i] = NULL;
15611         }
15612
15613         storedGames++;
15614         forwardMostMove = firstMove; // truncate game so we can start variation
15615         if(storedGames == 1) GreyRevert(FALSE);
15616 }
15617
15618 Boolean
15619 PopTail(Boolean annotate)
15620 {
15621         int i, j, nrMoves;
15622         char buf[8000], moveBuf[20];
15623
15624         if(appData.icsActive) return FALSE; // only in local mode
15625         if(!storedGames) return FALSE; // sanity
15626         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15627
15628         storedGames--;
15629         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15630         nrMoves = savedLast[storedGames] - currentMove;
15631         if(annotate) {
15632                 int cnt = 10;
15633                 if(!WhiteOnMove(currentMove))
15634                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15635                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15636                 for(i=currentMove; i<forwardMostMove; i++) {
15637                         if(WhiteOnMove(i))
15638                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15639                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15640                         strcat(buf, moveBuf);
15641                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15642                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15643                 }
15644                 strcat(buf, ")");
15645         }
15646         for(i=1; i<=nrMoves; i++) { // copy last variation back
15647             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15648             for(j=0; j<MOVE_LEN; j++)
15649                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15650             for(j=0; j<2*MOVE_LEN; j++)
15651                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15652             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15653             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15654             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15655             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15656             commentList[currentMove+i] = commentList[framePtr+i];
15657             commentList[framePtr+i] = NULL;
15658         }
15659         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15660         framePtr = savedFramePtr[storedGames];
15661         gameInfo.result = savedResult[storedGames];
15662         if(gameInfo.resultDetails != NULL) {
15663             free(gameInfo.resultDetails);
15664       }
15665         gameInfo.resultDetails = savedDetails[storedGames];
15666         forwardMostMove = currentMove + nrMoves;
15667         if(storedGames == 0) GreyRevert(TRUE);
15668         return TRUE;
15669 }
15670
15671 void
15672 CleanupTail()
15673 {       // remove all shelved variations
15674         int i;
15675         for(i=0; i<storedGames; i++) {
15676             if(savedDetails[i])
15677                 free(savedDetails[i]);
15678             savedDetails[i] = NULL;
15679         }
15680         for(i=framePtr; i<MAX_MOVES; i++) {
15681                 if(commentList[i]) free(commentList[i]);
15682                 commentList[i] = NULL;
15683         }
15684         framePtr = MAX_MOVES-1;
15685         storedGames = 0;
15686 }
15687
15688 void
15689 LoadVariation(int index, char *text)
15690 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15691         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15692         int level = 0, move;
15693
15694         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15695         // first find outermost bracketing variation
15696         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15697             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15698                 if(*p == '{') wait = '}'; else
15699                 if(*p == '[') wait = ']'; else
15700                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15701                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15702             }
15703             if(*p == wait) wait = NULLCHAR; // closing ]} found
15704             p++;
15705         }
15706         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15707         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15708         end[1] = NULLCHAR; // clip off comment beyond variation
15709         ToNrEvent(currentMove-1);
15710         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15711         // kludge: use ParsePV() to append variation to game
15712         move = currentMove;
15713         ParsePV(start, TRUE);
15714         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15715         ClearPremoveHighlights();
15716         CommentPopDown();
15717         ToNrEvent(currentMove+1);
15718 }
15719