07897c8cdd7f490517608abc41ca1c49827e98e5
[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 CommonEngineInit()
663 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664     if (appData.firstPlaysBlack) {
665         first.twoMachinesColor = "black\n";
666         second.twoMachinesColor = "white\n";
667     } else {
668         first.twoMachinesColor = "white\n";
669         second.twoMachinesColor = "black\n";
670     }
671
672     first.other = &second;
673     second.other = &first;
674
675     { float norm = 1;
676         if(appData.timeOddsMode) {
677             norm = appData.timeOdds[0];
678             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
679         }
680         first.timeOdds  = appData.timeOdds[0]/norm;
681         second.timeOdds = appData.timeOdds[1]/norm;
682     }
683
684     if(programVersion) free(programVersion);
685     if (appData.noChessProgram) {
686         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687         sprintf(programVersion, "%s", PACKAGE_STRING);
688     } else {
689       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
692     }
693 }
694
695 void
696 UnloadEngine(ChessProgramState *cps)
697 {
698         /* Kill off first chess program */
699         if (cps->isr != NULL)
700           RemoveInputSource(cps->isr);
701         cps->isr = NULL;
702
703         if (cps->pr != NoProc) {
704             ExitAnalyzeMode();
705             DoSleep( appData.delayBeforeQuit );
706             SendToProgram("quit\n", cps);
707             DoSleep( appData.delayAfterQuit );
708             DestroyChildProcess(cps->pr, cps->useSigterm);
709         }
710         cps->pr = NoProc;
711 }
712
713 void
714 ClearOptions(ChessProgramState *cps)
715 {
716     int i;
717     cps->nrOptions = cps->comboCnt = 0;
718     for(i=0; i<MAX_OPTIONS; i++) {
719         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
720         cps->option[i].textValue = 0;
721     }
722 }
723
724 char *engineNames[] = {
725 "first",
726 "second"
727 };
728
729 InitEngine(ChessProgramState *cps, int n)
730 {   // [HGM] all engine initialiation put in a function that does one engine
731
732     ClearOptions(cps);
733
734     cps->which = engineNames[n];
735     cps->maybeThinking = FALSE;
736     cps->pr = NoProc;
737     cps->isr = NULL;
738     cps->sendTime = 2;
739     cps->sendDrawOffers = 1;
740
741     cps->program = appData.chessProgram[n];
742     cps->host = appData.host[n];
743     cps->dir = appData.directory[n];
744     cps->initString = appData.engInitString[n];
745     cps->computerString = appData.computerString[n];
746     cps->useSigint  = TRUE;
747     cps->useSigterm = TRUE;
748     cps->reuse = appData.reuse[n];
749     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
750     cps->useSetboard = FALSE;
751     cps->useSAN = FALSE;
752     cps->usePing = FALSE;
753     cps->lastPing = 0;
754     cps->lastPong = 0;
755     cps->usePlayother = FALSE;
756     cps->useColors = TRUE;
757     cps->useUsermove = FALSE;
758     cps->sendICS = FALSE;
759     cps->sendName = appData.icsActive;
760     cps->sdKludge = FALSE;
761     cps->stKludge = FALSE;
762     TidyProgramName(cps->program, cps->host, cps->tidy);
763     cps->matchWins = 0;
764     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
765     cps->analysisSupport = 2; /* detect */
766     cps->analyzing = FALSE;
767     cps->initDone = FALSE;
768
769     /* New features added by Tord: */
770     cps->useFEN960 = FALSE;
771     cps->useOOCastle = TRUE;
772     /* End of new features added by Tord. */
773     cps->fenOverride  = appData.fenOverride[n];
774
775     /* [HGM] time odds: set factor for each machine */
776     cps->timeOdds  = appData.timeOdds[n];
777
778     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
779     cps->accumulateTC = appData.accumulateTC[n];
780     cps->maxNrOfSessions = 1;
781
782     /* [HGM] debug */
783     cps->debug = FALSE;
784     cps->supportsNPS = UNKNOWN;
785
786     /* [HGM] options */
787     cps->optionSettings  = appData.engOptions[n];
788
789     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
790     cps->isUCI = appData.isUCI[n]; /* [AS] */
791     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
792
793     if (appData.protocolVersion[n] > PROTOVER
794         || appData.protocolVersion[n] < 1)
795       {
796         char buf[MSG_SIZ];
797         int len;
798
799         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
800                        appData.protocolVersion[n]);
801         if( (len > MSG_SIZ) && appData.debugMode )
802           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
803
804         DisplayFatalError(buf, 0, 2);
805       }
806     else
807       {
808         cps->protocolVersion = appData.protocolVersion[n];
809       }
810
811     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
812 }
813
814 void
815 InitBackEnd1()
816 {
817     int matched, min, sec;
818
819     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
820     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
821
822     GetTimeMark(&programStartTime);
823     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
824
825     ClearProgramStats();
826     programStats.ok_to_send = 1;
827     programStats.seen_stat = 0;
828
829     /*
830      * Initialize game list
831      */
832     ListNew(&gameList);
833
834
835     /*
836      * Internet chess server status
837      */
838     if (appData.icsActive) {
839         appData.matchMode = FALSE;
840         appData.matchGames = 0;
841 #if ZIPPY
842         appData.noChessProgram = !appData.zippyPlay;
843 #else
844         appData.zippyPlay = FALSE;
845         appData.zippyTalk = FALSE;
846         appData.noChessProgram = TRUE;
847 #endif
848         if (*appData.icsHelper != NULLCHAR) {
849             appData.useTelnet = TRUE;
850             appData.telnetProgram = appData.icsHelper;
851         }
852     } else {
853         appData.zippyTalk = appData.zippyPlay = FALSE;
854     }
855
856     /* [AS] Initialize pv info list [HGM] and game state */
857     {
858         int i, j;
859
860         for( i=0; i<=framePtr; i++ ) {
861             pvInfoList[i].depth = -1;
862             boards[i][EP_STATUS] = EP_NONE;
863             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
864         }
865     }
866
867     /*
868      * Parse timeControl resource
869      */
870     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
871                           appData.movesPerSession)) {
872         char buf[MSG_SIZ];
873         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
874         DisplayFatalError(buf, 0, 2);
875     }
876
877     /*
878      * Parse searchTime resource
879      */
880     if (*appData.searchTime != NULLCHAR) {
881         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
882         if (matched == 1) {
883             searchTime = min * 60;
884         } else if (matched == 2) {
885             searchTime = min * 60 + sec;
886         } else {
887             char buf[MSG_SIZ];
888             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
889             DisplayFatalError(buf, 0, 2);
890         }
891     }
892
893     /* [AS] Adjudication threshold */
894     adjudicateLossThreshold = appData.adjudicateLossThreshold;
895
896     InitEngine(&first, 0);
897     InitEngine(&second, 1);
898     CommonEngineInit();
899
900     if (appData.icsActive) {
901         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
902     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
903         appData.clockMode = FALSE;
904         first.sendTime = second.sendTime = 0;
905     }
906
907 #if ZIPPY
908     /* Override some settings from environment variables, for backward
909        compatibility.  Unfortunately it's not feasible to have the env
910        vars just set defaults, at least in xboard.  Ugh.
911     */
912     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
913       ZippyInit();
914     }
915 #endif
916
917     if (!appData.icsActive) {
918       char buf[MSG_SIZ];
919       int len;
920
921       /* Check for variants that are supported only in ICS mode,
922          or not at all.  Some that are accepted here nevertheless
923          have bugs; see comments below.
924       */
925       VariantClass variant = StringToVariant(appData.variant);
926       switch (variant) {
927       case VariantBughouse:     /* need four players and two boards */
928       case VariantKriegspiel:   /* need to hide pieces and move details */
929         /* case VariantFischeRandom: (Fabien: moved below) */
930         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
931         if( (len > MSG_SIZ) && appData.debugMode )
932           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
933
934         DisplayFatalError(buf, 0, 2);
935         return;
936
937       case VariantUnknown:
938       case VariantLoadable:
939       case Variant29:
940       case Variant30:
941       case Variant31:
942       case Variant32:
943       case Variant33:
944       case Variant34:
945       case Variant35:
946       case Variant36:
947       default:
948         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
949         if( (len > MSG_SIZ) && appData.debugMode )
950           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
951
952         DisplayFatalError(buf, 0, 2);
953         return;
954
955       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
956       case VariantFairy:      /* [HGM] TestLegality definitely off! */
957       case VariantGothic:     /* [HGM] should work */
958       case VariantCapablanca: /* [HGM] should work */
959       case VariantCourier:    /* [HGM] initial forced moves not implemented */
960       case VariantShogi:      /* [HGM] could still mate with pawn drop */
961       case VariantKnightmate: /* [HGM] should work */
962       case VariantCylinder:   /* [HGM] untested */
963       case VariantFalcon:     /* [HGM] untested */
964       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
965                                  offboard interposition not understood */
966       case VariantNormal:     /* definitely works! */
967       case VariantWildCastle: /* pieces not automatically shuffled */
968       case VariantNoCastle:   /* pieces not automatically shuffled */
969       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
970       case VariantLosers:     /* should work except for win condition,
971                                  and doesn't know captures are mandatory */
972       case VariantSuicide:    /* should work except for win condition,
973                                  and doesn't know captures are mandatory */
974       case VariantGiveaway:   /* should work except for win condition,
975                                  and doesn't know captures are mandatory */
976       case VariantTwoKings:   /* should work */
977       case VariantAtomic:     /* should work except for win condition */
978       case Variant3Check:     /* should work except for win condition */
979       case VariantShatranj:   /* should work except for all win conditions */
980       case VariantMakruk:     /* should work except for daw countdown */
981       case VariantBerolina:   /* might work if TestLegality is off */
982       case VariantCapaRandom: /* should work */
983       case VariantJanus:      /* should work */
984       case VariantSuper:      /* experimental */
985       case VariantGreat:      /* experimental, requires legality testing to be off */
986       case VariantSChess:     /* S-Chess, should work */
987       case VariantSpartan:    /* should work */
988         break;
989       }
990     }
991
992 }
993
994 int NextIntegerFromString( char ** str, long * value )
995 {
996     int result = -1;
997     char * s = *str;
998
999     while( *s == ' ' || *s == '\t' ) {
1000         s++;
1001     }
1002
1003     *value = 0;
1004
1005     if( *s >= '0' && *s <= '9' ) {
1006         while( *s >= '0' && *s <= '9' ) {
1007             *value = *value * 10 + (*s - '0');
1008             s++;
1009         }
1010
1011         result = 0;
1012     }
1013
1014     *str = s;
1015
1016     return result;
1017 }
1018
1019 int NextTimeControlFromString( char ** str, long * value )
1020 {
1021     long temp;
1022     int result = NextIntegerFromString( str, &temp );
1023
1024     if( result == 0 ) {
1025         *value = temp * 60; /* Minutes */
1026         if( **str == ':' ) {
1027             (*str)++;
1028             result = NextIntegerFromString( str, &temp );
1029             *value += temp; /* Seconds */
1030         }
1031     }
1032
1033     return result;
1034 }
1035
1036 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1037 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1038     int result = -1, type = 0; long temp, temp2;
1039
1040     if(**str != ':') return -1; // old params remain in force!
1041     (*str)++;
1042     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1043     if( NextIntegerFromString( str, &temp ) ) return -1;
1044     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1045
1046     if(**str != '/') {
1047         /* time only: incremental or sudden-death time control */
1048         if(**str == '+') { /* increment follows; read it */
1049             (*str)++;
1050             if(**str == '!') type = *(*str)++; // Bronstein TC
1051             if(result = NextIntegerFromString( str, &temp2)) return -1;
1052             *inc = temp2 * 1000;
1053             if(**str == '.') { // read fraction of increment
1054                 char *start = ++(*str);
1055                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1056                 temp2 *= 1000;
1057                 while(start++ < *str) temp2 /= 10;
1058                 *inc += temp2;
1059             }
1060         } else *inc = 0;
1061         *moves = 0; *tc = temp * 1000; *incType = type;
1062         return 0;
1063     }
1064
1065     (*str)++; /* classical time control */
1066     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1067
1068     if(result == 0) {
1069         *moves = temp;
1070         *tc    = temp2 * 1000;
1071         *inc   = 0;
1072         *incType = type;
1073     }
1074     return result;
1075 }
1076
1077 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1078 {   /* [HGM] get time to add from the multi-session time-control string */
1079     int incType, moves=1; /* kludge to force reading of first session */
1080     long time, increment;
1081     char *s = tcString;
1082
1083     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1084     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1085     do {
1086         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1087         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1088         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1089         if(movenr == -1) return time;    /* last move before new session     */
1090         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1091         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1092         if(!moves) return increment;     /* current session is incremental   */
1093         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1094     } while(movenr >= -1);               /* try again for next session       */
1095
1096     return 0; // no new time quota on this move
1097 }
1098
1099 int
1100 ParseTimeControl(tc, ti, mps)
1101      char *tc;
1102      float ti;
1103      int mps;
1104 {
1105   long tc1;
1106   long tc2;
1107   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1108   int min, sec=0;
1109
1110   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1111   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1112       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1113   if(ti > 0) {
1114
1115     if(mps)
1116       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1117     else 
1118       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1119   } else {
1120     if(mps)
1121       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1122     else 
1123       snprintf(buf, MSG_SIZ, ":%s", mytc);
1124   }
1125   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1126   
1127   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1128     return FALSE;
1129   }
1130
1131   if( *tc == '/' ) {
1132     /* Parse second time control */
1133     tc++;
1134
1135     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1136       return FALSE;
1137     }
1138
1139     if( tc2 == 0 ) {
1140       return FALSE;
1141     }
1142
1143     timeControl_2 = tc2 * 1000;
1144   }
1145   else {
1146     timeControl_2 = 0;
1147   }
1148
1149   if( tc1 == 0 ) {
1150     return FALSE;
1151   }
1152
1153   timeControl = tc1 * 1000;
1154
1155   if (ti >= 0) {
1156     timeIncrement = ti * 1000;  /* convert to ms */
1157     movesPerSession = 0;
1158   } else {
1159     timeIncrement = 0;
1160     movesPerSession = mps;
1161   }
1162   return TRUE;
1163 }
1164
1165 void
1166 InitBackEnd2()
1167 {
1168     if (appData.debugMode) {
1169         fprintf(debugFP, "%s\n", programVersion);
1170     }
1171
1172     set_cont_sequence(appData.wrapContSeq);
1173     if (appData.matchGames > 0) {
1174         appData.matchMode = TRUE;
1175     } else if (appData.matchMode) {
1176         appData.matchGames = 1;
1177     }
1178     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1179         appData.matchGames = appData.sameColorGames;
1180     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1181         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1182         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1183     }
1184     Reset(TRUE, FALSE);
1185     if (appData.noChessProgram || first.protocolVersion == 1) {
1186       InitBackEnd3();
1187     } else {
1188       /* kludge: allow timeout for initial "feature" commands */
1189       FreezeUI();
1190       DisplayMessage("", _("Starting chess program"));
1191       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1192     }
1193 }
1194
1195 void
1196 MatchEvent(int mode)
1197 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1198         /* Set up machine vs. machine match */
1199         if (appData.noChessProgram) {
1200             DisplayFatalError(_("Can't have a match with no chess programs"),
1201                               0, 2);
1202             return;
1203         }
1204         matchMode = mode;
1205         matchGame = 1;
1206         if (*appData.loadGameFile != NULLCHAR) {
1207             int index = appData.loadGameIndex; // [HGM] autoinc
1208             if(index<0) lastIndex = index = 1;
1209             if (!LoadGameFromFile(appData.loadGameFile,
1210                                   index,
1211                                   appData.loadGameFile, FALSE)) {
1212                 DisplayFatalError(_("Bad game file"), 0, 1);
1213                 return;
1214             }
1215         } else if (*appData.loadPositionFile != NULLCHAR) {
1216             int index = appData.loadPositionIndex; // [HGM] autoinc
1217             if(index<0) lastIndex = index = 1;
1218             if (!LoadPositionFromFile(appData.loadPositionFile,
1219                                       index,
1220                                       appData.loadPositionFile)) {
1221                 DisplayFatalError(_("Bad position file"), 0, 1);
1222                 return;
1223             }
1224         }
1225         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1226         TwoMachinesEvent();
1227 }
1228
1229 void
1230 InitBackEnd3 P((void))
1231 {
1232     GameMode initialMode;
1233     char buf[MSG_SIZ];
1234     int err, len;
1235
1236     InitChessProgram(&first, startedFromSetupPosition);
1237
1238     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1239         free(programVersion);
1240         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1241         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1242     }
1243
1244     if (appData.icsActive) {
1245 #ifdef WIN32
1246         /* [DM] Make a console window if needed [HGM] merged ifs */
1247         ConsoleCreate();
1248 #endif
1249         err = establish();
1250         if (err != 0)
1251           {
1252             if (*appData.icsCommPort != NULLCHAR)
1253               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1254                              appData.icsCommPort);
1255             else
1256               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1257                         appData.icsHost, appData.icsPort);
1258
1259             if( (len > MSG_SIZ) && appData.debugMode )
1260               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1261
1262             DisplayFatalError(buf, err, 1);
1263             return;
1264         }
1265         SetICSMode();
1266         telnetISR =
1267           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1268         fromUserISR =
1269           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1270         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1271             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1272     } else if (appData.noChessProgram) {
1273         SetNCPMode();
1274     } else {
1275         SetGNUMode();
1276     }
1277
1278     if (*appData.cmailGameName != NULLCHAR) {
1279         SetCmailMode();
1280         OpenLoopback(&cmailPR);
1281         cmailISR =
1282           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1283     }
1284
1285     ThawUI();
1286     DisplayMessage("", "");
1287     if (StrCaseCmp(appData.initialMode, "") == 0) {
1288       initialMode = BeginningOfGame;
1289     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1290       initialMode = TwoMachinesPlay;
1291     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1292       initialMode = AnalyzeFile;
1293     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1294       initialMode = AnalyzeMode;
1295     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1296       initialMode = MachinePlaysWhite;
1297     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1298       initialMode = MachinePlaysBlack;
1299     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1300       initialMode = EditGame;
1301     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1302       initialMode = EditPosition;
1303     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1304       initialMode = Training;
1305     } else {
1306       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1307       if( (len > MSG_SIZ) && appData.debugMode )
1308         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1309
1310       DisplayFatalError(buf, 0, 2);
1311       return;
1312     }
1313
1314     if (appData.matchMode) {
1315         MatchEvent(TRUE);
1316     } else if (*appData.cmailGameName != NULLCHAR) {
1317         /* Set up cmail mode */
1318         ReloadCmailMsgEvent(TRUE);
1319     } else {
1320         /* Set up other modes */
1321         if (initialMode == AnalyzeFile) {
1322           if (*appData.loadGameFile == NULLCHAR) {
1323             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1324             return;
1325           }
1326         }
1327         if (*appData.loadGameFile != NULLCHAR) {
1328             (void) LoadGameFromFile(appData.loadGameFile,
1329                                     appData.loadGameIndex,
1330                                     appData.loadGameFile, TRUE);
1331         } else if (*appData.loadPositionFile != NULLCHAR) {
1332             (void) LoadPositionFromFile(appData.loadPositionFile,
1333                                         appData.loadPositionIndex,
1334                                         appData.loadPositionFile);
1335             /* [HGM] try to make self-starting even after FEN load */
1336             /* to allow automatic setup of fairy variants with wtm */
1337             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1338                 gameMode = BeginningOfGame;
1339                 setboardSpoiledMachineBlack = 1;
1340             }
1341             /* [HGM] loadPos: make that every new game uses the setup */
1342             /* from file as long as we do not switch variant          */
1343             if(!blackPlaysFirst) {
1344                 startedFromPositionFile = TRUE;
1345                 CopyBoard(filePosition, boards[0]);
1346             }
1347         }
1348         if (initialMode == AnalyzeMode) {
1349           if (appData.noChessProgram) {
1350             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1351             return;
1352           }
1353           if (appData.icsActive) {
1354             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1355             return;
1356           }
1357           AnalyzeModeEvent();
1358         } else if (initialMode == AnalyzeFile) {
1359           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1360           ShowThinkingEvent();
1361           AnalyzeFileEvent();
1362           AnalysisPeriodicEvent(1);
1363         } else if (initialMode == MachinePlaysWhite) {
1364           if (appData.noChessProgram) {
1365             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1366                               0, 2);
1367             return;
1368           }
1369           if (appData.icsActive) {
1370             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1371                               0, 2);
1372             return;
1373           }
1374           MachineWhiteEvent();
1375         } else if (initialMode == MachinePlaysBlack) {
1376           if (appData.noChessProgram) {
1377             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1378                               0, 2);
1379             return;
1380           }
1381           if (appData.icsActive) {
1382             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1383                               0, 2);
1384             return;
1385           }
1386           MachineBlackEvent();
1387         } else if (initialMode == TwoMachinesPlay) {
1388           if (appData.noChessProgram) {
1389             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1390                               0, 2);
1391             return;
1392           }
1393           if (appData.icsActive) {
1394             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1395                               0, 2);
1396             return;
1397           }
1398           TwoMachinesEvent();
1399         } else if (initialMode == EditGame) {
1400           EditGameEvent();
1401         } else if (initialMode == EditPosition) {
1402           EditPositionEvent();
1403         } else if (initialMode == Training) {
1404           if (*appData.loadGameFile == NULLCHAR) {
1405             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1406             return;
1407           }
1408           TrainingEvent();
1409         }
1410     }
1411 }
1412
1413 /*
1414  * Establish will establish a contact to a remote host.port.
1415  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1416  *  used to talk to the host.
1417  * Returns 0 if okay, error code if not.
1418  */
1419 int
1420 establish()
1421 {
1422     char buf[MSG_SIZ];
1423
1424     if (*appData.icsCommPort != NULLCHAR) {
1425         /* Talk to the host through a serial comm port */
1426         return OpenCommPort(appData.icsCommPort, &icsPR);
1427
1428     } else if (*appData.gateway != NULLCHAR) {
1429         if (*appData.remoteShell == NULLCHAR) {
1430             /* Use the rcmd protocol to run telnet program on a gateway host */
1431             snprintf(buf, sizeof(buf), "%s %s %s",
1432                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1433             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1434
1435         } else {
1436             /* Use the rsh program to run telnet program on a gateway host */
1437             if (*appData.remoteUser == NULLCHAR) {
1438                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1439                         appData.gateway, appData.telnetProgram,
1440                         appData.icsHost, appData.icsPort);
1441             } else {
1442                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1443                         appData.remoteShell, appData.gateway,
1444                         appData.remoteUser, appData.telnetProgram,
1445                         appData.icsHost, appData.icsPort);
1446             }
1447             return StartChildProcess(buf, "", &icsPR);
1448
1449         }
1450     } else if (appData.useTelnet) {
1451         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1452
1453     } else {
1454         /* TCP socket interface differs somewhat between
1455            Unix and NT; handle details in the front end.
1456            */
1457         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1458     }
1459 }
1460
1461 void EscapeExpand(char *p, char *q)
1462 {       // [HGM] initstring: routine to shape up string arguments
1463         while(*p++ = *q++) if(p[-1] == '\\')
1464             switch(*q++) {
1465                 case 'n': p[-1] = '\n'; break;
1466                 case 'r': p[-1] = '\r'; break;
1467                 case 't': p[-1] = '\t'; break;
1468                 case '\\': p[-1] = '\\'; break;
1469                 case 0: *p = 0; return;
1470                 default: p[-1] = q[-1]; break;
1471             }
1472 }
1473
1474 void
1475 show_bytes(fp, buf, count)
1476      FILE *fp;
1477      char *buf;
1478      int count;
1479 {
1480     while (count--) {
1481         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1482             fprintf(fp, "\\%03o", *buf & 0xff);
1483         } else {
1484             putc(*buf, fp);
1485         }
1486         buf++;
1487     }
1488     fflush(fp);
1489 }
1490
1491 /* Returns an errno value */
1492 int
1493 OutputMaybeTelnet(pr, message, count, outError)
1494      ProcRef pr;
1495      char *message;
1496      int count;
1497      int *outError;
1498 {
1499     char buf[8192], *p, *q, *buflim;
1500     int left, newcount, outcount;
1501
1502     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1503         *appData.gateway != NULLCHAR) {
1504         if (appData.debugMode) {
1505             fprintf(debugFP, ">ICS: ");
1506             show_bytes(debugFP, message, count);
1507             fprintf(debugFP, "\n");
1508         }
1509         return OutputToProcess(pr, message, count, outError);
1510     }
1511
1512     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1513     p = message;
1514     q = buf;
1515     left = count;
1516     newcount = 0;
1517     while (left) {
1518         if (q >= buflim) {
1519             if (appData.debugMode) {
1520                 fprintf(debugFP, ">ICS: ");
1521                 show_bytes(debugFP, buf, newcount);
1522                 fprintf(debugFP, "\n");
1523             }
1524             outcount = OutputToProcess(pr, buf, newcount, outError);
1525             if (outcount < newcount) return -1; /* to be sure */
1526             q = buf;
1527             newcount = 0;
1528         }
1529         if (*p == '\n') {
1530             *q++ = '\r';
1531             newcount++;
1532         } else if (((unsigned char) *p) == TN_IAC) {
1533             *q++ = (char) TN_IAC;
1534             newcount ++;
1535         }
1536         *q++ = *p++;
1537         newcount++;
1538         left--;
1539     }
1540     if (appData.debugMode) {
1541         fprintf(debugFP, ">ICS: ");
1542         show_bytes(debugFP, buf, newcount);
1543         fprintf(debugFP, "\n");
1544     }
1545     outcount = OutputToProcess(pr, buf, newcount, outError);
1546     if (outcount < newcount) return -1; /* to be sure */
1547     return count;
1548 }
1549
1550 void
1551 read_from_player(isr, closure, message, count, error)
1552      InputSourceRef isr;
1553      VOIDSTAR closure;
1554      char *message;
1555      int count;
1556      int error;
1557 {
1558     int outError, outCount;
1559     static int gotEof = 0;
1560
1561     /* Pass data read from player on to ICS */
1562     if (count > 0) {
1563         gotEof = 0;
1564         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1565         if (outCount < count) {
1566             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1567         }
1568     } else if (count < 0) {
1569         RemoveInputSource(isr);
1570         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1571     } else if (gotEof++ > 0) {
1572         RemoveInputSource(isr);
1573         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1574     }
1575 }
1576
1577 void
1578 KeepAlive()
1579 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1580     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1581     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1582     SendToICS("date\n");
1583     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1584 }
1585
1586 /* added routine for printf style output to ics */
1587 void ics_printf(char *format, ...)
1588 {
1589     char buffer[MSG_SIZ];
1590     va_list args;
1591
1592     va_start(args, format);
1593     vsnprintf(buffer, sizeof(buffer), format, args);
1594     buffer[sizeof(buffer)-1] = '\0';
1595     SendToICS(buffer);
1596     va_end(args);
1597 }
1598
1599 void
1600 SendToICS(s)
1601      char *s;
1602 {
1603     int count, outCount, outError;
1604
1605     if (icsPR == NULL) return;
1606
1607     count = strlen(s);
1608     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1609     if (outCount < count) {
1610         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1611     }
1612 }
1613
1614 /* This is used for sending logon scripts to the ICS. Sending
1615    without a delay causes problems when using timestamp on ICC
1616    (at least on my machine). */
1617 void
1618 SendToICSDelayed(s,msdelay)
1619      char *s;
1620      long msdelay;
1621 {
1622     int count, outCount, outError;
1623
1624     if (icsPR == NULL) return;
1625
1626     count = strlen(s);
1627     if (appData.debugMode) {
1628         fprintf(debugFP, ">ICS: ");
1629         show_bytes(debugFP, s, count);
1630         fprintf(debugFP, "\n");
1631     }
1632     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1633                                       msdelay);
1634     if (outCount < count) {
1635         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1636     }
1637 }
1638
1639
1640 /* Remove all highlighting escape sequences in s
1641    Also deletes any suffix starting with '('
1642    */
1643 char *
1644 StripHighlightAndTitle(s)
1645      char *s;
1646 {
1647     static char retbuf[MSG_SIZ];
1648     char *p = retbuf;
1649
1650     while (*s != NULLCHAR) {
1651         while (*s == '\033') {
1652             while (*s != NULLCHAR && !isalpha(*s)) s++;
1653             if (*s != NULLCHAR) s++;
1654         }
1655         while (*s != NULLCHAR && *s != '\033') {
1656             if (*s == '(' || *s == '[') {
1657                 *p = NULLCHAR;
1658                 return retbuf;
1659             }
1660             *p++ = *s++;
1661         }
1662     }
1663     *p = NULLCHAR;
1664     return retbuf;
1665 }
1666
1667 /* Remove all highlighting escape sequences in s */
1668 char *
1669 StripHighlight(s)
1670      char *s;
1671 {
1672     static char retbuf[MSG_SIZ];
1673     char *p = retbuf;
1674
1675     while (*s != NULLCHAR) {
1676         while (*s == '\033') {
1677             while (*s != NULLCHAR && !isalpha(*s)) s++;
1678             if (*s != NULLCHAR) s++;
1679         }
1680         while (*s != NULLCHAR && *s != '\033') {
1681             *p++ = *s++;
1682         }
1683     }
1684     *p = NULLCHAR;
1685     return retbuf;
1686 }
1687
1688 char *variantNames[] = VARIANT_NAMES;
1689 char *
1690 VariantName(v)
1691      VariantClass v;
1692 {
1693     return variantNames[v];
1694 }
1695
1696
1697 /* Identify a variant from the strings the chess servers use or the
1698    PGN Variant tag names we use. */
1699 VariantClass
1700 StringToVariant(e)
1701      char *e;
1702 {
1703     char *p;
1704     int wnum = -1;
1705     VariantClass v = VariantNormal;
1706     int i, found = FALSE;
1707     char buf[MSG_SIZ];
1708     int len;
1709
1710     if (!e) return v;
1711
1712     /* [HGM] skip over optional board-size prefixes */
1713     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1714         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1715         while( *e++ != '_');
1716     }
1717
1718     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1719         v = VariantNormal;
1720         found = TRUE;
1721     } else
1722     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1723       if (StrCaseStr(e, variantNames[i])) {
1724         v = (VariantClass) i;
1725         found = TRUE;
1726         break;
1727       }
1728     }
1729
1730     if (!found) {
1731       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1732           || StrCaseStr(e, "wild/fr")
1733           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1734         v = VariantFischeRandom;
1735       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1736                  (i = 1, p = StrCaseStr(e, "w"))) {
1737         p += i;
1738         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1739         if (isdigit(*p)) {
1740           wnum = atoi(p);
1741         } else {
1742           wnum = -1;
1743         }
1744         switch (wnum) {
1745         case 0: /* FICS only, actually */
1746         case 1:
1747           /* Castling legal even if K starts on d-file */
1748           v = VariantWildCastle;
1749           break;
1750         case 2:
1751         case 3:
1752         case 4:
1753           /* Castling illegal even if K & R happen to start in
1754              normal positions. */
1755           v = VariantNoCastle;
1756           break;
1757         case 5:
1758         case 7:
1759         case 8:
1760         case 10:
1761         case 11:
1762         case 12:
1763         case 13:
1764         case 14:
1765         case 15:
1766         case 18:
1767         case 19:
1768           /* Castling legal iff K & R start in normal positions */
1769           v = VariantNormal;
1770           break;
1771         case 6:
1772         case 20:
1773         case 21:
1774           /* Special wilds for position setup; unclear what to do here */
1775           v = VariantLoadable;
1776           break;
1777         case 9:
1778           /* Bizarre ICC game */
1779           v = VariantTwoKings;
1780           break;
1781         case 16:
1782           v = VariantKriegspiel;
1783           break;
1784         case 17:
1785           v = VariantLosers;
1786           break;
1787         case 22:
1788           v = VariantFischeRandom;
1789           break;
1790         case 23:
1791           v = VariantCrazyhouse;
1792           break;
1793         case 24:
1794           v = VariantBughouse;
1795           break;
1796         case 25:
1797           v = Variant3Check;
1798           break;
1799         case 26:
1800           /* Not quite the same as FICS suicide! */
1801           v = VariantGiveaway;
1802           break;
1803         case 27:
1804           v = VariantAtomic;
1805           break;
1806         case 28:
1807           v = VariantShatranj;
1808           break;
1809
1810         /* Temporary names for future ICC types.  The name *will* change in
1811            the next xboard/WinBoard release after ICC defines it. */
1812         case 29:
1813           v = Variant29;
1814           break;
1815         case 30:
1816           v = Variant30;
1817           break;
1818         case 31:
1819           v = Variant31;
1820           break;
1821         case 32:
1822           v = Variant32;
1823           break;
1824         case 33:
1825           v = Variant33;
1826           break;
1827         case 34:
1828           v = Variant34;
1829           break;
1830         case 35:
1831           v = Variant35;
1832           break;
1833         case 36:
1834           v = Variant36;
1835           break;
1836         case 37:
1837           v = VariantShogi;
1838           break;
1839         case 38:
1840           v = VariantXiangqi;
1841           break;
1842         case 39:
1843           v = VariantCourier;
1844           break;
1845         case 40:
1846           v = VariantGothic;
1847           break;
1848         case 41:
1849           v = VariantCapablanca;
1850           break;
1851         case 42:
1852           v = VariantKnightmate;
1853           break;
1854         case 43:
1855           v = VariantFairy;
1856           break;
1857         case 44:
1858           v = VariantCylinder;
1859           break;
1860         case 45:
1861           v = VariantFalcon;
1862           break;
1863         case 46:
1864           v = VariantCapaRandom;
1865           break;
1866         case 47:
1867           v = VariantBerolina;
1868           break;
1869         case 48:
1870           v = VariantJanus;
1871           break;
1872         case 49:
1873           v = VariantSuper;
1874           break;
1875         case 50:
1876           v = VariantGreat;
1877           break;
1878         case -1:
1879           /* Found "wild" or "w" in the string but no number;
1880              must assume it's normal chess. */
1881           v = VariantNormal;
1882           break;
1883         default:
1884           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1885           if( (len > MSG_SIZ) && appData.debugMode )
1886             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1887
1888           DisplayError(buf, 0);
1889           v = VariantUnknown;
1890           break;
1891         }
1892       }
1893     }
1894     if (appData.debugMode) {
1895       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1896               e, wnum, VariantName(v));
1897     }
1898     return v;
1899 }
1900
1901 static int leftover_start = 0, leftover_len = 0;
1902 char star_match[STAR_MATCH_N][MSG_SIZ];
1903
1904 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1905    advance *index beyond it, and set leftover_start to the new value of
1906    *index; else return FALSE.  If pattern contains the character '*', it
1907    matches any sequence of characters not containing '\r', '\n', or the
1908    character following the '*' (if any), and the matched sequence(s) are
1909    copied into star_match.
1910    */
1911 int
1912 looking_at(buf, index, pattern)
1913      char *buf;
1914      int *index;
1915      char *pattern;
1916 {
1917     char *bufp = &buf[*index], *patternp = pattern;
1918     int star_count = 0;
1919     char *matchp = star_match[0];
1920
1921     for (;;) {
1922         if (*patternp == NULLCHAR) {
1923             *index = leftover_start = bufp - buf;
1924             *matchp = NULLCHAR;
1925             return TRUE;
1926         }
1927         if (*bufp == NULLCHAR) return FALSE;
1928         if (*patternp == '*') {
1929             if (*bufp == *(patternp + 1)) {
1930                 *matchp = NULLCHAR;
1931                 matchp = star_match[++star_count];
1932                 patternp += 2;
1933                 bufp++;
1934                 continue;
1935             } else if (*bufp == '\n' || *bufp == '\r') {
1936                 patternp++;
1937                 if (*patternp == NULLCHAR)
1938                   continue;
1939                 else
1940                   return FALSE;
1941             } else {
1942                 *matchp++ = *bufp++;
1943                 continue;
1944             }
1945         }
1946         if (*patternp != *bufp) return FALSE;
1947         patternp++;
1948         bufp++;
1949     }
1950 }
1951
1952 void
1953 SendToPlayer(data, length)
1954      char *data;
1955      int length;
1956 {
1957     int error, outCount;
1958     outCount = OutputToProcess(NoProc, data, length, &error);
1959     if (outCount < length) {
1960         DisplayFatalError(_("Error writing to display"), error, 1);
1961     }
1962 }
1963
1964 void
1965 PackHolding(packed, holding)
1966      char packed[];
1967      char *holding;
1968 {
1969     char *p = holding;
1970     char *q = packed;
1971     int runlength = 0;
1972     int curr = 9999;
1973     do {
1974         if (*p == curr) {
1975             runlength++;
1976         } else {
1977             switch (runlength) {
1978               case 0:
1979                 break;
1980               case 1:
1981                 *q++ = curr;
1982                 break;
1983               case 2:
1984                 *q++ = curr;
1985                 *q++ = curr;
1986                 break;
1987               default:
1988                 sprintf(q, "%d", runlength);
1989                 while (*q) q++;
1990                 *q++ = curr;
1991                 break;
1992             }
1993             runlength = 1;
1994             curr = *p;
1995         }
1996     } while (*p++);
1997     *q = NULLCHAR;
1998 }
1999
2000 /* Telnet protocol requests from the front end */
2001 void
2002 TelnetRequest(ddww, option)
2003      unsigned char ddww, option;
2004 {
2005     unsigned char msg[3];
2006     int outCount, outError;
2007
2008     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2009
2010     if (appData.debugMode) {
2011         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2012         switch (ddww) {
2013           case TN_DO:
2014             ddwwStr = "DO";
2015             break;
2016           case TN_DONT:
2017             ddwwStr = "DONT";
2018             break;
2019           case TN_WILL:
2020             ddwwStr = "WILL";
2021             break;
2022           case TN_WONT:
2023             ddwwStr = "WONT";
2024             break;
2025           default:
2026             ddwwStr = buf1;
2027             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2028             break;
2029         }
2030         switch (option) {
2031           case TN_ECHO:
2032             optionStr = "ECHO";
2033             break;
2034           default:
2035             optionStr = buf2;
2036             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2037             break;
2038         }
2039         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2040     }
2041     msg[0] = TN_IAC;
2042     msg[1] = ddww;
2043     msg[2] = option;
2044     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2045     if (outCount < 3) {
2046         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2047     }
2048 }
2049
2050 void
2051 DoEcho()
2052 {
2053     if (!appData.icsActive) return;
2054     TelnetRequest(TN_DO, TN_ECHO);
2055 }
2056
2057 void
2058 DontEcho()
2059 {
2060     if (!appData.icsActive) return;
2061     TelnetRequest(TN_DONT, TN_ECHO);
2062 }
2063
2064 void
2065 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2066 {
2067     /* put the holdings sent to us by the server on the board holdings area */
2068     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2069     char p;
2070     ChessSquare piece;
2071
2072     if(gameInfo.holdingsWidth < 2)  return;
2073     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2074         return; // prevent overwriting by pre-board holdings
2075
2076     if( (int)lowestPiece >= BlackPawn ) {
2077         holdingsColumn = 0;
2078         countsColumn = 1;
2079         holdingsStartRow = BOARD_HEIGHT-1;
2080         direction = -1;
2081     } else {
2082         holdingsColumn = BOARD_WIDTH-1;
2083         countsColumn = BOARD_WIDTH-2;
2084         holdingsStartRow = 0;
2085         direction = 1;
2086     }
2087
2088     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2089         board[i][holdingsColumn] = EmptySquare;
2090         board[i][countsColumn]   = (ChessSquare) 0;
2091     }
2092     while( (p=*holdings++) != NULLCHAR ) {
2093         piece = CharToPiece( ToUpper(p) );
2094         if(piece == EmptySquare) continue;
2095         /*j = (int) piece - (int) WhitePawn;*/
2096         j = PieceToNumber(piece);
2097         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2098         if(j < 0) continue;               /* should not happen */
2099         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2100         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2101         board[holdingsStartRow+j*direction][countsColumn]++;
2102     }
2103 }
2104
2105
2106 void
2107 VariantSwitch(Board board, VariantClass newVariant)
2108 {
2109    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2110    static Board oldBoard;
2111
2112    startedFromPositionFile = FALSE;
2113    if(gameInfo.variant == newVariant) return;
2114
2115    /* [HGM] This routine is called each time an assignment is made to
2116     * gameInfo.variant during a game, to make sure the board sizes
2117     * are set to match the new variant. If that means adding or deleting
2118     * holdings, we shift the playing board accordingly
2119     * This kludge is needed because in ICS observe mode, we get boards
2120     * of an ongoing game without knowing the variant, and learn about the
2121     * latter only later. This can be because of the move list we requested,
2122     * in which case the game history is refilled from the beginning anyway,
2123     * but also when receiving holdings of a crazyhouse game. In the latter
2124     * case we want to add those holdings to the already received position.
2125     */
2126
2127
2128    if (appData.debugMode) {
2129      fprintf(debugFP, "Switch board from %s to %s\n",
2130              VariantName(gameInfo.variant), VariantName(newVariant));
2131      setbuf(debugFP, NULL);
2132    }
2133    shuffleOpenings = 0;       /* [HGM] shuffle */
2134    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2135    switch(newVariant)
2136      {
2137      case VariantShogi:
2138        newWidth = 9;  newHeight = 9;
2139        gameInfo.holdingsSize = 7;
2140      case VariantBughouse:
2141      case VariantCrazyhouse:
2142        newHoldingsWidth = 2; break;
2143      case VariantGreat:
2144        newWidth = 10;
2145      case VariantSuper:
2146        newHoldingsWidth = 2;
2147        gameInfo.holdingsSize = 8;
2148        break;
2149      case VariantGothic:
2150      case VariantCapablanca:
2151      case VariantCapaRandom:
2152        newWidth = 10;
2153      default:
2154        newHoldingsWidth = gameInfo.holdingsSize = 0;
2155      };
2156
2157    if(newWidth  != gameInfo.boardWidth  ||
2158       newHeight != gameInfo.boardHeight ||
2159       newHoldingsWidth != gameInfo.holdingsWidth ) {
2160
2161      /* shift position to new playing area, if needed */
2162      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2163        for(i=0; i<BOARD_HEIGHT; i++)
2164          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2165            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2166              board[i][j];
2167        for(i=0; i<newHeight; i++) {
2168          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2169          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2170        }
2171      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2172        for(i=0; i<BOARD_HEIGHT; i++)
2173          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2174            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2175              board[i][j];
2176      }
2177      gameInfo.boardWidth  = newWidth;
2178      gameInfo.boardHeight = newHeight;
2179      gameInfo.holdingsWidth = newHoldingsWidth;
2180      gameInfo.variant = newVariant;
2181      InitDrawingSizes(-2, 0);
2182    } else gameInfo.variant = newVariant;
2183    CopyBoard(oldBoard, board);   // remember correctly formatted board
2184      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2185    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2186 }
2187
2188 static int loggedOn = FALSE;
2189
2190 /*-- Game start info cache: --*/
2191 int gs_gamenum;
2192 char gs_kind[MSG_SIZ];
2193 static char player1Name[128] = "";
2194 static char player2Name[128] = "";
2195 static char cont_seq[] = "\n\\   ";
2196 static int player1Rating = -1;
2197 static int player2Rating = -1;
2198 /*----------------------------*/
2199
2200 ColorClass curColor = ColorNormal;
2201 int suppressKibitz = 0;
2202
2203 // [HGM] seekgraph
2204 Boolean soughtPending = FALSE;
2205 Boolean seekGraphUp;
2206 #define MAX_SEEK_ADS 200
2207 #define SQUARE 0x80
2208 char *seekAdList[MAX_SEEK_ADS];
2209 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2210 float tcList[MAX_SEEK_ADS];
2211 char colorList[MAX_SEEK_ADS];
2212 int nrOfSeekAds = 0;
2213 int minRating = 1010, maxRating = 2800;
2214 int hMargin = 10, vMargin = 20, h, w;
2215 extern int squareSize, lineGap;
2216
2217 void
2218 PlotSeekAd(int i)
2219 {
2220         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2221         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2222         if(r < minRating+100 && r >=0 ) r = minRating+100;
2223         if(r > maxRating) r = maxRating;
2224         if(tc < 1.) tc = 1.;
2225         if(tc > 95.) tc = 95.;
2226         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2227         y = ((double)r - minRating)/(maxRating - minRating)
2228             * (h-vMargin-squareSize/8-1) + vMargin;
2229         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2230         if(strstr(seekAdList[i], " u ")) color = 1;
2231         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2232            !strstr(seekAdList[i], "bullet") &&
2233            !strstr(seekAdList[i], "blitz") &&
2234            !strstr(seekAdList[i], "standard") ) color = 2;
2235         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2236         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2237 }
2238
2239 void
2240 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2241 {
2242         char buf[MSG_SIZ], *ext = "";
2243         VariantClass v = StringToVariant(type);
2244         if(strstr(type, "wild")) {
2245             ext = type + 4; // append wild number
2246             if(v == VariantFischeRandom) type = "chess960"; else
2247             if(v == VariantLoadable) type = "setup"; else
2248             type = VariantName(v);
2249         }
2250         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2251         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2252             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2253             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2254             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2255             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2256             seekNrList[nrOfSeekAds] = nr;
2257             zList[nrOfSeekAds] = 0;
2258             seekAdList[nrOfSeekAds++] = StrSave(buf);
2259             if(plot) PlotSeekAd(nrOfSeekAds-1);
2260         }
2261 }
2262
2263 void
2264 EraseSeekDot(int i)
2265 {
2266     int x = xList[i], y = yList[i], d=squareSize/4, k;
2267     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2268     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2269     // now replot every dot that overlapped
2270     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2271         int xx = xList[k], yy = yList[k];
2272         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2273             DrawSeekDot(xx, yy, colorList[k]);
2274     }
2275 }
2276
2277 void
2278 RemoveSeekAd(int nr)
2279 {
2280         int i;
2281         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2282             EraseSeekDot(i);
2283             if(seekAdList[i]) free(seekAdList[i]);
2284             seekAdList[i] = seekAdList[--nrOfSeekAds];
2285             seekNrList[i] = seekNrList[nrOfSeekAds];
2286             ratingList[i] = ratingList[nrOfSeekAds];
2287             colorList[i]  = colorList[nrOfSeekAds];
2288             tcList[i] = tcList[nrOfSeekAds];
2289             xList[i]  = xList[nrOfSeekAds];
2290             yList[i]  = yList[nrOfSeekAds];
2291             zList[i]  = zList[nrOfSeekAds];
2292             seekAdList[nrOfSeekAds] = NULL;
2293             break;
2294         }
2295 }
2296
2297 Boolean
2298 MatchSoughtLine(char *line)
2299 {
2300     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2301     int nr, base, inc, u=0; char dummy;
2302
2303     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2304        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2305        (u=1) &&
2306        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2307         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2308         // match: compact and save the line
2309         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2310         return TRUE;
2311     }
2312     return FALSE;
2313 }
2314
2315 int
2316 DrawSeekGraph()
2317 {
2318     int i;
2319     if(!seekGraphUp) return FALSE;
2320     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2321     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2322
2323     DrawSeekBackground(0, 0, w, h);
2324     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2325     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2326     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2327         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2328         yy = h-1-yy;
2329         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2330         if(i%500 == 0) {
2331             char buf[MSG_SIZ];
2332             snprintf(buf, MSG_SIZ, "%d", i);
2333             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2334         }
2335     }
2336     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2337     for(i=1; i<100; i+=(i<10?1:5)) {
2338         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2339         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2340         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2341             char buf[MSG_SIZ];
2342             snprintf(buf, MSG_SIZ, "%d", i);
2343             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2344         }
2345     }
2346     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2347     return TRUE;
2348 }
2349
2350 int SeekGraphClick(ClickType click, int x, int y, int moving)
2351 {
2352     static int lastDown = 0, displayed = 0, lastSecond;
2353     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2354         if(click == Release || moving) return FALSE;
2355         nrOfSeekAds = 0;
2356         soughtPending = TRUE;
2357         SendToICS(ics_prefix);
2358         SendToICS("sought\n"); // should this be "sought all"?
2359     } else { // issue challenge based on clicked ad
2360         int dist = 10000; int i, closest = 0, second = 0;
2361         for(i=0; i<nrOfSeekAds; i++) {
2362             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2363             if(d < dist) { dist = d; closest = i; }
2364             second += (d - zList[i] < 120); // count in-range ads
2365             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2366         }
2367         if(dist < 120) {
2368             char buf[MSG_SIZ];
2369             second = (second > 1);
2370             if(displayed != closest || second != lastSecond) {
2371                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2372                 lastSecond = second; displayed = closest;
2373             }
2374             if(click == Press) {
2375                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2376                 lastDown = closest;
2377                 return TRUE;
2378             } // on press 'hit', only show info
2379             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2380             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2381             SendToICS(ics_prefix);
2382             SendToICS(buf);
2383             return TRUE; // let incoming board of started game pop down the graph
2384         } else if(click == Release) { // release 'miss' is ignored
2385             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2386             if(moving == 2) { // right up-click
2387                 nrOfSeekAds = 0; // refresh graph
2388                 soughtPending = TRUE;
2389                 SendToICS(ics_prefix);
2390                 SendToICS("sought\n"); // should this be "sought all"?
2391             }
2392             return TRUE;
2393         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2394         // press miss or release hit 'pop down' seek graph
2395         seekGraphUp = FALSE;
2396         DrawPosition(TRUE, NULL);
2397     }
2398     return TRUE;
2399 }
2400
2401 void
2402 read_from_ics(isr, closure, data, count, error)
2403      InputSourceRef isr;
2404      VOIDSTAR closure;
2405      char *data;
2406      int count;
2407      int error;
2408 {
2409 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2410 #define STARTED_NONE 0
2411 #define STARTED_MOVES 1
2412 #define STARTED_BOARD 2
2413 #define STARTED_OBSERVE 3
2414 #define STARTED_HOLDINGS 4
2415 #define STARTED_CHATTER 5
2416 #define STARTED_COMMENT 6
2417 #define STARTED_MOVES_NOHIDE 7
2418
2419     static int started = STARTED_NONE;
2420     static char parse[20000];
2421     static int parse_pos = 0;
2422     static char buf[BUF_SIZE + 1];
2423     static int firstTime = TRUE, intfSet = FALSE;
2424     static ColorClass prevColor = ColorNormal;
2425     static int savingComment = FALSE;
2426     static int cmatch = 0; // continuation sequence match
2427     char *bp;
2428     char str[MSG_SIZ];
2429     int i, oldi;
2430     int buf_len;
2431     int next_out;
2432     int tkind;
2433     int backup;    /* [DM] For zippy color lines */
2434     char *p;
2435     char talker[MSG_SIZ]; // [HGM] chat
2436     int channel;
2437
2438     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2439
2440     if (appData.debugMode) {
2441       if (!error) {
2442         fprintf(debugFP, "<ICS: ");
2443         show_bytes(debugFP, data, count);
2444         fprintf(debugFP, "\n");
2445       }
2446     }
2447
2448     if (appData.debugMode) { int f = forwardMostMove;
2449         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2450                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2451                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2452     }
2453     if (count > 0) {
2454         /* If last read ended with a partial line that we couldn't parse,
2455            prepend it to the new read and try again. */
2456         if (leftover_len > 0) {
2457             for (i=0; i<leftover_len; i++)
2458               buf[i] = buf[leftover_start + i];
2459         }
2460
2461     /* copy new characters into the buffer */
2462     bp = buf + leftover_len;
2463     buf_len=leftover_len;
2464     for (i=0; i<count; i++)
2465     {
2466         // ignore these
2467         if (data[i] == '\r')
2468             continue;
2469
2470         // join lines split by ICS?
2471         if (!appData.noJoin)
2472         {
2473             /*
2474                 Joining just consists of finding matches against the
2475                 continuation sequence, and discarding that sequence
2476                 if found instead of copying it.  So, until a match
2477                 fails, there's nothing to do since it might be the
2478                 complete sequence, and thus, something we don't want
2479                 copied.
2480             */
2481             if (data[i] == cont_seq[cmatch])
2482             {
2483                 cmatch++;
2484                 if (cmatch == strlen(cont_seq))
2485                 {
2486                     cmatch = 0; // complete match.  just reset the counter
2487
2488                     /*
2489                         it's possible for the ICS to not include the space
2490                         at the end of the last word, making our [correct]
2491                         join operation fuse two separate words.  the server
2492                         does this when the space occurs at the width setting.
2493                     */
2494                     if (!buf_len || buf[buf_len-1] != ' ')
2495                     {
2496                         *bp++ = ' ';
2497                         buf_len++;
2498                     }
2499                 }
2500                 continue;
2501             }
2502             else if (cmatch)
2503             {
2504                 /*
2505                     match failed, so we have to copy what matched before
2506                     falling through and copying this character.  In reality,
2507                     this will only ever be just the newline character, but
2508                     it doesn't hurt to be precise.
2509                 */
2510                 strncpy(bp, cont_seq, cmatch);
2511                 bp += cmatch;
2512                 buf_len += cmatch;
2513                 cmatch = 0;
2514             }
2515         }
2516
2517         // copy this char
2518         *bp++ = data[i];
2519         buf_len++;
2520     }
2521
2522         buf[buf_len] = NULLCHAR;
2523 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2524         next_out = 0;
2525         leftover_start = 0;
2526
2527         i = 0;
2528         while (i < buf_len) {
2529             /* Deal with part of the TELNET option negotiation
2530                protocol.  We refuse to do anything beyond the
2531                defaults, except that we allow the WILL ECHO option,
2532                which ICS uses to turn off password echoing when we are
2533                directly connected to it.  We reject this option
2534                if localLineEditing mode is on (always on in xboard)
2535                and we are talking to port 23, which might be a real
2536                telnet server that will try to keep WILL ECHO on permanently.
2537              */
2538             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2539                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2540                 unsigned char option;
2541                 oldi = i;
2542                 switch ((unsigned char) buf[++i]) {
2543                   case TN_WILL:
2544                     if (appData.debugMode)
2545                       fprintf(debugFP, "\n<WILL ");
2546                     switch (option = (unsigned char) buf[++i]) {
2547                       case TN_ECHO:
2548                         if (appData.debugMode)
2549                           fprintf(debugFP, "ECHO ");
2550                         /* Reply only if this is a change, according
2551                            to the protocol rules. */
2552                         if (remoteEchoOption) break;
2553                         if (appData.localLineEditing &&
2554                             atoi(appData.icsPort) == TN_PORT) {
2555                             TelnetRequest(TN_DONT, TN_ECHO);
2556                         } else {
2557                             EchoOff();
2558                             TelnetRequest(TN_DO, TN_ECHO);
2559                             remoteEchoOption = TRUE;
2560                         }
2561                         break;
2562                       default:
2563                         if (appData.debugMode)
2564                           fprintf(debugFP, "%d ", option);
2565                         /* Whatever this is, we don't want it. */
2566                         TelnetRequest(TN_DONT, option);
2567                         break;
2568                     }
2569                     break;
2570                   case TN_WONT:
2571                     if (appData.debugMode)
2572                       fprintf(debugFP, "\n<WONT ");
2573                     switch (option = (unsigned char) buf[++i]) {
2574                       case TN_ECHO:
2575                         if (appData.debugMode)
2576                           fprintf(debugFP, "ECHO ");
2577                         /* Reply only if this is a change, according
2578                            to the protocol rules. */
2579                         if (!remoteEchoOption) break;
2580                         EchoOn();
2581                         TelnetRequest(TN_DONT, TN_ECHO);
2582                         remoteEchoOption = FALSE;
2583                         break;
2584                       default:
2585                         if (appData.debugMode)
2586                           fprintf(debugFP, "%d ", (unsigned char) option);
2587                         /* Whatever this is, it must already be turned
2588                            off, because we never agree to turn on
2589                            anything non-default, so according to the
2590                            protocol rules, we don't reply. */
2591                         break;
2592                     }
2593                     break;
2594                   case TN_DO:
2595                     if (appData.debugMode)
2596                       fprintf(debugFP, "\n<DO ");
2597                     switch (option = (unsigned char) buf[++i]) {
2598                       default:
2599                         /* Whatever this is, we refuse to do it. */
2600                         if (appData.debugMode)
2601                           fprintf(debugFP, "%d ", option);
2602                         TelnetRequest(TN_WONT, option);
2603                         break;
2604                     }
2605                     break;
2606                   case TN_DONT:
2607                     if (appData.debugMode)
2608                       fprintf(debugFP, "\n<DONT ");
2609                     switch (option = (unsigned char) buf[++i]) {
2610                       default:
2611                         if (appData.debugMode)
2612                           fprintf(debugFP, "%d ", option);
2613                         /* Whatever this is, we are already not doing
2614                            it, because we never agree to do anything
2615                            non-default, so according to the protocol
2616                            rules, we don't reply. */
2617                         break;
2618                     }
2619                     break;
2620                   case TN_IAC:
2621                     if (appData.debugMode)
2622                       fprintf(debugFP, "\n<IAC ");
2623                     /* Doubled IAC; pass it through */
2624                     i--;
2625                     break;
2626                   default:
2627                     if (appData.debugMode)
2628                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2629                     /* Drop all other telnet commands on the floor */
2630                     break;
2631                 }
2632                 if (oldi > next_out)
2633                   SendToPlayer(&buf[next_out], oldi - next_out);
2634                 if (++i > next_out)
2635                   next_out = i;
2636                 continue;
2637             }
2638
2639             /* OK, this at least will *usually* work */
2640             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2641                 loggedOn = TRUE;
2642             }
2643
2644             if (loggedOn && !intfSet) {
2645                 if (ics_type == ICS_ICC) {
2646                   snprintf(str, MSG_SIZ,
2647                           "/set-quietly interface %s\n/set-quietly style 12\n",
2648                           programVersion);
2649                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2650                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2651                 } else if (ics_type == ICS_CHESSNET) {
2652                   snprintf(str, MSG_SIZ, "/style 12\n");
2653                 } else {
2654                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2655                   strcat(str, programVersion);
2656                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2657                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2658                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2659 #ifdef WIN32
2660                   strcat(str, "$iset nohighlight 1\n");
2661 #endif
2662                   strcat(str, "$iset lock 1\n$style 12\n");
2663                 }
2664                 SendToICS(str);
2665                 NotifyFrontendLogin();
2666                 intfSet = TRUE;
2667             }
2668
2669             if (started == STARTED_COMMENT) {
2670                 /* Accumulate characters in comment */
2671                 parse[parse_pos++] = buf[i];
2672                 if (buf[i] == '\n') {
2673                     parse[parse_pos] = NULLCHAR;
2674                     if(chattingPartner>=0) {
2675                         char mess[MSG_SIZ];
2676                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2677                         OutputChatMessage(chattingPartner, mess);
2678                         chattingPartner = -1;
2679                         next_out = i+1; // [HGM] suppress printing in ICS window
2680                     } else
2681                     if(!suppressKibitz) // [HGM] kibitz
2682                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2683                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2684                         int nrDigit = 0, nrAlph = 0, j;
2685                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2686                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2687                         parse[parse_pos] = NULLCHAR;
2688                         // try to be smart: if it does not look like search info, it should go to
2689                         // ICS interaction window after all, not to engine-output window.
2690                         for(j=0; j<parse_pos; j++) { // count letters and digits
2691                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2692                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2693                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2694                         }
2695                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2696                             int depth=0; float score;
2697                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2698                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2699                                 pvInfoList[forwardMostMove-1].depth = depth;
2700                                 pvInfoList[forwardMostMove-1].score = 100*score;
2701                             }
2702                             OutputKibitz(suppressKibitz, parse);
2703                         } else {
2704                             char tmp[MSG_SIZ];
2705                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2706                             SendToPlayer(tmp, strlen(tmp));
2707                         }
2708                         next_out = i+1; // [HGM] suppress printing in ICS window
2709                     }
2710                     started = STARTED_NONE;
2711                 } else {
2712                     /* Don't match patterns against characters in comment */
2713                     i++;
2714                     continue;
2715                 }
2716             }
2717             if (started == STARTED_CHATTER) {
2718                 if (buf[i] != '\n') {
2719                     /* Don't match patterns against characters in chatter */
2720                     i++;
2721                     continue;
2722                 }
2723                 started = STARTED_NONE;
2724                 if(suppressKibitz) next_out = i+1;
2725             }
2726
2727             /* Kludge to deal with rcmd protocol */
2728             if (firstTime && looking_at(buf, &i, "\001*")) {
2729                 DisplayFatalError(&buf[1], 0, 1);
2730                 continue;
2731             } else {
2732                 firstTime = FALSE;
2733             }
2734
2735             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2736                 ics_type = ICS_ICC;
2737                 ics_prefix = "/";
2738                 if (appData.debugMode)
2739                   fprintf(debugFP, "ics_type %d\n", ics_type);
2740                 continue;
2741             }
2742             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2743                 ics_type = ICS_FICS;
2744                 ics_prefix = "$";
2745                 if (appData.debugMode)
2746                   fprintf(debugFP, "ics_type %d\n", ics_type);
2747                 continue;
2748             }
2749             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2750                 ics_type = ICS_CHESSNET;
2751                 ics_prefix = "/";
2752                 if (appData.debugMode)
2753                   fprintf(debugFP, "ics_type %d\n", ics_type);
2754                 continue;
2755             }
2756
2757             if (!loggedOn &&
2758                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2759                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2760                  looking_at(buf, &i, "will be \"*\""))) {
2761               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2762               continue;
2763             }
2764
2765             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2766               char buf[MSG_SIZ];
2767               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2768               DisplayIcsInteractionTitle(buf);
2769               have_set_title = TRUE;
2770             }
2771
2772             /* skip finger notes */
2773             if (started == STARTED_NONE &&
2774                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2775                  (buf[i] == '1' && buf[i+1] == '0')) &&
2776                 buf[i+2] == ':' && buf[i+3] == ' ') {
2777               started = STARTED_CHATTER;
2778               i += 3;
2779               continue;
2780             }
2781
2782             oldi = i;
2783             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2784             if(appData.seekGraph) {
2785                 if(soughtPending && MatchSoughtLine(buf+i)) {
2786                     i = strstr(buf+i, "rated") - buf;
2787                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2788                     next_out = leftover_start = i;
2789                     started = STARTED_CHATTER;
2790                     suppressKibitz = TRUE;
2791                     continue;
2792                 }
2793                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2794                         && looking_at(buf, &i, "* ads displayed")) {
2795                     soughtPending = FALSE;
2796                     seekGraphUp = TRUE;
2797                     DrawSeekGraph();
2798                     continue;
2799                 }
2800                 if(appData.autoRefresh) {
2801                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2802                         int s = (ics_type == ICS_ICC); // ICC format differs
2803                         if(seekGraphUp)
2804                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2805                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2806                         looking_at(buf, &i, "*% "); // eat prompt
2807                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2808                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2809                         next_out = i; // suppress
2810                         continue;
2811                     }
2812                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2813                         char *p = star_match[0];
2814                         while(*p) {
2815                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2816                             while(*p && *p++ != ' '); // next
2817                         }
2818                         looking_at(buf, &i, "*% "); // eat prompt
2819                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2820                         next_out = i;
2821                         continue;
2822                     }
2823                 }
2824             }
2825
2826             /* skip formula vars */
2827             if (started == STARTED_NONE &&
2828                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2829               started = STARTED_CHATTER;
2830               i += 3;
2831               continue;
2832             }
2833
2834             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2835             if (appData.autoKibitz && started == STARTED_NONE &&
2836                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2837                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2838                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2839                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2840                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2841                         suppressKibitz = TRUE;
2842                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2843                         next_out = i;
2844                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2845                                 && (gameMode == IcsPlayingWhite)) ||
2846                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2847                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2848                             started = STARTED_CHATTER; // own kibitz we simply discard
2849                         else {
2850                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2851                             parse_pos = 0; parse[0] = NULLCHAR;
2852                             savingComment = TRUE;
2853                             suppressKibitz = gameMode != IcsObserving ? 2 :
2854                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2855                         }
2856                         continue;
2857                 } else
2858                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2859                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2860                          && atoi(star_match[0])) {
2861                     // suppress the acknowledgements of our own autoKibitz
2862                     char *p;
2863                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2864                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2865                     SendToPlayer(star_match[0], strlen(star_match[0]));
2866                     if(looking_at(buf, &i, "*% ")) // eat prompt
2867                         suppressKibitz = FALSE;
2868                     next_out = i;
2869                     continue;
2870                 }
2871             } // [HGM] kibitz: end of patch
2872
2873             // [HGM] chat: intercept tells by users for which we have an open chat window
2874             channel = -1;
2875             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2876                                            looking_at(buf, &i, "* whispers:") ||
2877                                            looking_at(buf, &i, "* kibitzes:") ||
2878                                            looking_at(buf, &i, "* shouts:") ||
2879                                            looking_at(buf, &i, "* c-shouts:") ||
2880                                            looking_at(buf, &i, "--> * ") ||
2881                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2882                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2883                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2884                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2885                 int p;
2886                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2887                 chattingPartner = -1;
2888
2889                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2890                 for(p=0; p<MAX_CHAT; p++) {
2891                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2892                     talker[0] = '['; strcat(talker, "] ");
2893                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2894                     chattingPartner = p; break;
2895                     }
2896                 } else
2897                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2898                 for(p=0; p<MAX_CHAT; p++) {
2899                     if(!strcmp("kibitzes", chatPartner[p])) {
2900                         talker[0] = '['; strcat(talker, "] ");
2901                         chattingPartner = p; break;
2902                     }
2903                 } else
2904                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2905                 for(p=0; p<MAX_CHAT; p++) {
2906                     if(!strcmp("whispers", chatPartner[p])) {
2907                         talker[0] = '['; strcat(talker, "] ");
2908                         chattingPartner = p; break;
2909                     }
2910                 } else
2911                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2912                   if(buf[i-8] == '-' && buf[i-3] == 't')
2913                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2914                     if(!strcmp("c-shouts", chatPartner[p])) {
2915                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2916                         chattingPartner = p; break;
2917                     }
2918                   }
2919                   if(chattingPartner < 0)
2920                   for(p=0; p<MAX_CHAT; p++) {
2921                     if(!strcmp("shouts", chatPartner[p])) {
2922                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2923                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2924                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2925                         chattingPartner = p; break;
2926                     }
2927                   }
2928                 }
2929                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2930                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2931                     talker[0] = 0; Colorize(ColorTell, FALSE);
2932                     chattingPartner = p; break;
2933                 }
2934                 if(chattingPartner<0) i = oldi; else {
2935                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2936                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2937                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2938                     started = STARTED_COMMENT;
2939                     parse_pos = 0; parse[0] = NULLCHAR;
2940                     savingComment = 3 + chattingPartner; // counts as TRUE
2941                     suppressKibitz = TRUE;
2942                     continue;
2943                 }
2944             } // [HGM] chat: end of patch
2945
2946             if (appData.zippyTalk || appData.zippyPlay) {
2947                 /* [DM] Backup address for color zippy lines */
2948                 backup = i;
2949 #if ZIPPY
2950                if (loggedOn == TRUE)
2951                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2952                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2953 #endif
2954             } // [DM] 'else { ' deleted
2955                 if (
2956                     /* Regular tells and says */
2957                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2958                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2959                     looking_at(buf, &i, "* says: ") ||
2960                     /* Don't color "message" or "messages" output */
2961                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2962                     looking_at(buf, &i, "*. * at *:*: ") ||
2963                     looking_at(buf, &i, "--* (*:*): ") ||
2964                     /* Message notifications (same color as tells) */
2965                     looking_at(buf, &i, "* has left a message ") ||
2966                     looking_at(buf, &i, "* just sent you a message:\n") ||
2967                     /* Whispers and kibitzes */
2968                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2969                     looking_at(buf, &i, "* kibitzes: ") ||
2970                     /* Channel tells */
2971                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2972
2973                   if (tkind == 1 && strchr(star_match[0], ':')) {
2974                       /* Avoid "tells you:" spoofs in channels */
2975                      tkind = 3;
2976                   }
2977                   if (star_match[0][0] == NULLCHAR ||
2978                       strchr(star_match[0], ' ') ||
2979                       (tkind == 3 && strchr(star_match[1], ' '))) {
2980                     /* Reject bogus matches */
2981                     i = oldi;
2982                   } else {
2983                     if (appData.colorize) {
2984                       if (oldi > next_out) {
2985                         SendToPlayer(&buf[next_out], oldi - next_out);
2986                         next_out = oldi;
2987                       }
2988                       switch (tkind) {
2989                       case 1:
2990                         Colorize(ColorTell, FALSE);
2991                         curColor = ColorTell;
2992                         break;
2993                       case 2:
2994                         Colorize(ColorKibitz, FALSE);
2995                         curColor = ColorKibitz;
2996                         break;
2997                       case 3:
2998                         p = strrchr(star_match[1], '(');
2999                         if (p == NULL) {
3000                           p = star_match[1];
3001                         } else {
3002                           p++;
3003                         }
3004                         if (atoi(p) == 1) {
3005                           Colorize(ColorChannel1, FALSE);
3006                           curColor = ColorChannel1;
3007                         } else {
3008                           Colorize(ColorChannel, FALSE);
3009                           curColor = ColorChannel;
3010                         }
3011                         break;
3012                       case 5:
3013                         curColor = ColorNormal;
3014                         break;
3015                       }
3016                     }
3017                     if (started == STARTED_NONE && appData.autoComment &&
3018                         (gameMode == IcsObserving ||
3019                          gameMode == IcsPlayingWhite ||
3020                          gameMode == IcsPlayingBlack)) {
3021                       parse_pos = i - oldi;
3022                       memcpy(parse, &buf[oldi], parse_pos);
3023                       parse[parse_pos] = NULLCHAR;
3024                       started = STARTED_COMMENT;
3025                       savingComment = TRUE;
3026                     } else {
3027                       started = STARTED_CHATTER;
3028                       savingComment = FALSE;
3029                     }
3030                     loggedOn = TRUE;
3031                     continue;
3032                   }
3033                 }
3034
3035                 if (looking_at(buf, &i, "* s-shouts: ") ||
3036                     looking_at(buf, &i, "* c-shouts: ")) {
3037                     if (appData.colorize) {
3038                         if (oldi > next_out) {
3039                             SendToPlayer(&buf[next_out], oldi - next_out);
3040                             next_out = oldi;
3041                         }
3042                         Colorize(ColorSShout, FALSE);
3043                         curColor = ColorSShout;
3044                     }
3045                     loggedOn = TRUE;
3046                     started = STARTED_CHATTER;
3047                     continue;
3048                 }
3049
3050                 if (looking_at(buf, &i, "--->")) {
3051                     loggedOn = TRUE;
3052                     continue;
3053                 }
3054
3055                 if (looking_at(buf, &i, "* shouts: ") ||
3056                     looking_at(buf, &i, "--> ")) {
3057                     if (appData.colorize) {
3058                         if (oldi > next_out) {
3059                             SendToPlayer(&buf[next_out], oldi - next_out);
3060                             next_out = oldi;
3061                         }
3062                         Colorize(ColorShout, FALSE);
3063                         curColor = ColorShout;
3064                     }
3065                     loggedOn = TRUE;
3066                     started = STARTED_CHATTER;
3067                     continue;
3068                 }
3069
3070                 if (looking_at( buf, &i, "Challenge:")) {
3071                     if (appData.colorize) {
3072                         if (oldi > next_out) {
3073                             SendToPlayer(&buf[next_out], oldi - next_out);
3074                             next_out = oldi;
3075                         }
3076                         Colorize(ColorChallenge, FALSE);
3077                         curColor = ColorChallenge;
3078                     }
3079                     loggedOn = TRUE;
3080                     continue;
3081                 }
3082
3083                 if (looking_at(buf, &i, "* offers you") ||
3084                     looking_at(buf, &i, "* offers to be") ||
3085                     looking_at(buf, &i, "* would like to") ||
3086                     looking_at(buf, &i, "* requests to") ||
3087                     looking_at(buf, &i, "Your opponent offers") ||
3088                     looking_at(buf, &i, "Your opponent requests")) {
3089
3090                     if (appData.colorize) {
3091                         if (oldi > next_out) {
3092                             SendToPlayer(&buf[next_out], oldi - next_out);
3093                             next_out = oldi;
3094                         }
3095                         Colorize(ColorRequest, FALSE);
3096                         curColor = ColorRequest;
3097                     }
3098                     continue;
3099                 }
3100
3101                 if (looking_at(buf, &i, "* (*) seeking")) {
3102                     if (appData.colorize) {
3103                         if (oldi > next_out) {
3104                             SendToPlayer(&buf[next_out], oldi - next_out);
3105                             next_out = oldi;
3106                         }
3107                         Colorize(ColorSeek, FALSE);
3108                         curColor = ColorSeek;
3109                     }
3110                     continue;
3111             }
3112
3113             if (looking_at(buf, &i, "\\   ")) {
3114                 if (prevColor != ColorNormal) {
3115                     if (oldi > next_out) {
3116                         SendToPlayer(&buf[next_out], oldi - next_out);
3117                         next_out = oldi;
3118                     }
3119                     Colorize(prevColor, TRUE);
3120                     curColor = prevColor;
3121                 }
3122                 if (savingComment) {
3123                     parse_pos = i - oldi;
3124                     memcpy(parse, &buf[oldi], parse_pos);
3125                     parse[parse_pos] = NULLCHAR;
3126                     started = STARTED_COMMENT;
3127                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3128                         chattingPartner = savingComment - 3; // kludge to remember the box
3129                 } else {
3130                     started = STARTED_CHATTER;
3131                 }
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "Black Strength :") ||
3136                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3137                 looking_at(buf, &i, "<10>") ||
3138                 looking_at(buf, &i, "#@#")) {
3139                 /* Wrong board style */
3140                 loggedOn = TRUE;
3141                 SendToICS(ics_prefix);
3142                 SendToICS("set style 12\n");
3143                 SendToICS(ics_prefix);
3144                 SendToICS("refresh\n");
3145                 continue;
3146             }
3147
3148             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3149                 ICSInitScript();
3150                 have_sent_ICS_logon = 1;
3151                 continue;
3152             }
3153
3154             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3155                 (looking_at(buf, &i, "\n<12> ") ||
3156                  looking_at(buf, &i, "<12> "))) {
3157                 loggedOn = TRUE;
3158                 if (oldi > next_out) {
3159                     SendToPlayer(&buf[next_out], oldi - next_out);
3160                 }
3161                 next_out = i;
3162                 started = STARTED_BOARD;
3163                 parse_pos = 0;
3164                 continue;
3165             }
3166
3167             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3168                 looking_at(buf, &i, "<b1> ")) {
3169                 if (oldi > next_out) {
3170                     SendToPlayer(&buf[next_out], oldi - next_out);
3171                 }
3172                 next_out = i;
3173                 started = STARTED_HOLDINGS;
3174                 parse_pos = 0;
3175                 continue;
3176             }
3177
3178             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3179                 loggedOn = TRUE;
3180                 /* Header for a move list -- first line */
3181
3182                 switch (ics_getting_history) {
3183                   case H_FALSE:
3184                     switch (gameMode) {
3185                       case IcsIdle:
3186                       case BeginningOfGame:
3187                         /* User typed "moves" or "oldmoves" while we
3188                            were idle.  Pretend we asked for these
3189                            moves and soak them up so user can step
3190                            through them and/or save them.
3191                            */
3192                         Reset(FALSE, TRUE);
3193                         gameMode = IcsObserving;
3194                         ModeHighlight();
3195                         ics_gamenum = -1;
3196                         ics_getting_history = H_GOT_UNREQ_HEADER;
3197                         break;
3198                       case EditGame: /*?*/
3199                       case EditPosition: /*?*/
3200                         /* Should above feature work in these modes too? */
3201                         /* For now it doesn't */
3202                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3203                         break;
3204                       default:
3205                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3206                         break;
3207                     }
3208                     break;
3209                   case H_REQUESTED:
3210                     /* Is this the right one? */
3211                     if (gameInfo.white && gameInfo.black &&
3212                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3213                         strcmp(gameInfo.black, star_match[2]) == 0) {
3214                         /* All is well */
3215                         ics_getting_history = H_GOT_REQ_HEADER;
3216                     }
3217                     break;
3218                   case H_GOT_REQ_HEADER:
3219                   case H_GOT_UNREQ_HEADER:
3220                   case H_GOT_UNWANTED_HEADER:
3221                   case H_GETTING_MOVES:
3222                     /* Should not happen */
3223                     DisplayError(_("Error gathering move list: two headers"), 0);
3224                     ics_getting_history = H_FALSE;
3225                     break;
3226                 }
3227
3228                 /* Save player ratings into gameInfo if needed */
3229                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3230                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3231                     (gameInfo.whiteRating == -1 ||
3232                      gameInfo.blackRating == -1)) {
3233
3234                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3235                     gameInfo.blackRating = string_to_rating(star_match[3]);
3236                     if (appData.debugMode)
3237                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3238                               gameInfo.whiteRating, gameInfo.blackRating);
3239                 }
3240                 continue;
3241             }
3242
3243             if (looking_at(buf, &i,
3244               "* * match, initial time: * minute*, increment: * second")) {
3245                 /* Header for a move list -- second line */
3246                 /* Initial board will follow if this is a wild game */
3247                 if (gameInfo.event != NULL) free(gameInfo.event);
3248                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3249                 gameInfo.event = StrSave(str);
3250                 /* [HGM] we switched variant. Translate boards if needed. */
3251                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3252                 continue;
3253             }
3254
3255             if (looking_at(buf, &i, "Move  ")) {
3256                 /* Beginning of a move list */
3257                 switch (ics_getting_history) {
3258                   case H_FALSE:
3259                     /* Normally should not happen */
3260                     /* Maybe user hit reset while we were parsing */
3261                     break;
3262                   case H_REQUESTED:
3263                     /* Happens if we are ignoring a move list that is not
3264                      * the one we just requested.  Common if the user
3265                      * tries to observe two games without turning off
3266                      * getMoveList */
3267                     break;
3268                   case H_GETTING_MOVES:
3269                     /* Should not happen */
3270                     DisplayError(_("Error gathering move list: nested"), 0);
3271                     ics_getting_history = H_FALSE;
3272                     break;
3273                   case H_GOT_REQ_HEADER:
3274                     ics_getting_history = H_GETTING_MOVES;
3275                     started = STARTED_MOVES;
3276                     parse_pos = 0;
3277                     if (oldi > next_out) {
3278                         SendToPlayer(&buf[next_out], oldi - next_out);
3279                     }
3280                     break;
3281                   case H_GOT_UNREQ_HEADER:
3282                     ics_getting_history = H_GETTING_MOVES;
3283                     started = STARTED_MOVES_NOHIDE;
3284                     parse_pos = 0;
3285                     break;
3286                   case H_GOT_UNWANTED_HEADER:
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289                 }
3290                 continue;
3291             }
3292
3293             if (looking_at(buf, &i, "% ") ||
3294                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3295                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3296                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3297                     soughtPending = FALSE;
3298                     seekGraphUp = TRUE;
3299                     DrawSeekGraph();
3300                 }
3301                 if(suppressKibitz) next_out = i;
3302                 savingComment = FALSE;
3303                 suppressKibitz = 0;
3304                 switch (started) {
3305                   case STARTED_MOVES:
3306                   case STARTED_MOVES_NOHIDE:
3307                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3308                     parse[parse_pos + i - oldi] = NULLCHAR;
3309                     ParseGameHistory(parse);
3310 #if ZIPPY
3311                     if (appData.zippyPlay && first.initDone) {
3312                         FeedMovesToProgram(&first, forwardMostMove);
3313                         if (gameMode == IcsPlayingWhite) {
3314                             if (WhiteOnMove(forwardMostMove)) {
3315                                 if (first.sendTime) {
3316                                   if (first.useColors) {
3317                                     SendToProgram("black\n", &first);
3318                                   }
3319                                   SendTimeRemaining(&first, TRUE);
3320                                 }
3321                                 if (first.useColors) {
3322                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3323                                 }
3324                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3325                                 first.maybeThinking = TRUE;
3326                             } else {
3327                                 if (first.usePlayother) {
3328                                   if (first.sendTime) {
3329                                     SendTimeRemaining(&first, TRUE);
3330                                   }
3331                                   SendToProgram("playother\n", &first);
3332                                   firstMove = FALSE;
3333                                 } else {
3334                                   firstMove = TRUE;
3335                                 }
3336                             }
3337                         } else if (gameMode == IcsPlayingBlack) {
3338                             if (!WhiteOnMove(forwardMostMove)) {
3339                                 if (first.sendTime) {
3340                                   if (first.useColors) {
3341                                     SendToProgram("white\n", &first);
3342                                   }
3343                                   SendTimeRemaining(&first, FALSE);
3344                                 }
3345                                 if (first.useColors) {
3346                                   SendToProgram("black\n", &first);
3347                                 }
3348                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3349                                 first.maybeThinking = TRUE;
3350                             } else {
3351                                 if (first.usePlayother) {
3352                                   if (first.sendTime) {
3353                                     SendTimeRemaining(&first, FALSE);
3354                                   }
3355                                   SendToProgram("playother\n", &first);
3356                                   firstMove = FALSE;
3357                                 } else {
3358                                   firstMove = TRUE;
3359                                 }
3360                             }
3361                         }
3362                     }
3363 #endif
3364                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3365                         /* Moves came from oldmoves or moves command
3366                            while we weren't doing anything else.
3367                            */
3368                         currentMove = forwardMostMove;
3369                         ClearHighlights();/*!!could figure this out*/
3370                         flipView = appData.flipView;
3371                         DrawPosition(TRUE, boards[currentMove]);
3372                         DisplayBothClocks();
3373                         snprintf(str, MSG_SIZ, "%s vs. %s",
3374                                 gameInfo.white, gameInfo.black);
3375                         DisplayTitle(str);
3376                         gameMode = IcsIdle;
3377                     } else {
3378                         /* Moves were history of an active game */
3379                         if (gameInfo.resultDetails != NULL) {
3380                             free(gameInfo.resultDetails);
3381                             gameInfo.resultDetails = NULL;
3382                         }
3383                     }
3384                     HistorySet(parseList, backwardMostMove,
3385                                forwardMostMove, currentMove-1);
3386                     DisplayMove(currentMove - 1);
3387                     if (started == STARTED_MOVES) next_out = i;
3388                     started = STARTED_NONE;
3389                     ics_getting_history = H_FALSE;
3390                     break;
3391
3392                   case STARTED_OBSERVE:
3393                     started = STARTED_NONE;
3394                     SendToICS(ics_prefix);
3395                     SendToICS("refresh\n");
3396                     break;
3397
3398                   default:
3399                     break;
3400                 }
3401                 if(bookHit) { // [HGM] book: simulate book reply
3402                     static char bookMove[MSG_SIZ]; // a bit generous?
3403
3404                     programStats.nodes = programStats.depth = programStats.time =
3405                     programStats.score = programStats.got_only_move = 0;
3406                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3407
3408                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3409                     strcat(bookMove, bookHit);
3410                     HandleMachineMove(bookMove, &first);
3411                 }
3412                 continue;
3413             }
3414
3415             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3416                  started == STARTED_HOLDINGS ||
3417                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3418                 /* Accumulate characters in move list or board */
3419                 parse[parse_pos++] = buf[i];
3420             }
3421
3422             /* Start of game messages.  Mostly we detect start of game
3423                when the first board image arrives.  On some versions
3424                of the ICS, though, we need to do a "refresh" after starting
3425                to observe in order to get the current board right away. */
3426             if (looking_at(buf, &i, "Adding game * to observation list")) {
3427                 started = STARTED_OBSERVE;
3428                 continue;
3429             }
3430
3431             /* Handle auto-observe */
3432             if (appData.autoObserve &&
3433                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3434                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3435                 char *player;
3436                 /* Choose the player that was highlighted, if any. */
3437                 if (star_match[0][0] == '\033' ||
3438                     star_match[1][0] != '\033') {
3439                     player = star_match[0];
3440                 } else {
3441                     player = star_match[2];
3442                 }
3443                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3444                         ics_prefix, StripHighlightAndTitle(player));
3445                 SendToICS(str);
3446
3447                 /* Save ratings from notify string */
3448                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3449                 player1Rating = string_to_rating(star_match[1]);
3450                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3451                 player2Rating = string_to_rating(star_match[3]);
3452
3453                 if (appData.debugMode)
3454                   fprintf(debugFP,
3455                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3456                           player1Name, player1Rating,
3457                           player2Name, player2Rating);
3458
3459                 continue;
3460             }
3461
3462             /* Deal with automatic examine mode after a game,
3463                and with IcsObserving -> IcsExamining transition */
3464             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3465                 looking_at(buf, &i, "has made you an examiner of game *")) {
3466
3467                 int gamenum = atoi(star_match[0]);
3468                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3469                     gamenum == ics_gamenum) {
3470                     /* We were already playing or observing this game;
3471                        no need to refetch history */
3472                     gameMode = IcsExamining;
3473                     if (pausing) {
3474                         pauseExamForwardMostMove = forwardMostMove;
3475                     } else if (currentMove < forwardMostMove) {
3476                         ForwardInner(forwardMostMove);
3477                     }
3478                 } else {
3479                     /* I don't think this case really can happen */
3480                     SendToICS(ics_prefix);
3481                     SendToICS("refresh\n");
3482                 }
3483                 continue;
3484             }
3485
3486             /* Error messages */
3487 //          if (ics_user_moved) {
3488             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3489                 if (looking_at(buf, &i, "Illegal move") ||
3490                     looking_at(buf, &i, "Not a legal move") ||
3491                     looking_at(buf, &i, "Your king is in check") ||
3492                     looking_at(buf, &i, "It isn't your turn") ||
3493                     looking_at(buf, &i, "It is not your move")) {
3494                     /* Illegal move */
3495                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3496                         currentMove = forwardMostMove-1;
3497                         DisplayMove(currentMove - 1); /* before DMError */
3498                         DrawPosition(FALSE, boards[currentMove]);
3499                         SwitchClocks(forwardMostMove-1); // [HGM] race
3500                         DisplayBothClocks();
3501                     }
3502                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3503                     ics_user_moved = 0;
3504                     continue;
3505                 }
3506             }
3507
3508             if (looking_at(buf, &i, "still have time") ||
3509                 looking_at(buf, &i, "not out of time") ||
3510                 looking_at(buf, &i, "either player is out of time") ||
3511                 looking_at(buf, &i, "has timeseal; checking")) {
3512                 /* We must have called his flag a little too soon */
3513                 whiteFlag = blackFlag = FALSE;
3514                 continue;
3515             }
3516
3517             if (looking_at(buf, &i, "added * seconds to") ||
3518                 looking_at(buf, &i, "seconds were added to")) {
3519                 /* Update the clocks */
3520                 SendToICS(ics_prefix);
3521                 SendToICS("refresh\n");
3522                 continue;
3523             }
3524
3525             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3526                 ics_clock_paused = TRUE;
3527                 StopClocks();
3528                 continue;
3529             }
3530
3531             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3532                 ics_clock_paused = FALSE;
3533                 StartClocks();
3534                 continue;
3535             }
3536
3537             /* Grab player ratings from the Creating: message.
3538                Note we have to check for the special case when
3539                the ICS inserts things like [white] or [black]. */
3540             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3541                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3542                 /* star_matches:
3543                    0    player 1 name (not necessarily white)
3544                    1    player 1 rating
3545                    2    empty, white, or black (IGNORED)
3546                    3    player 2 name (not necessarily black)
3547                    4    player 2 rating
3548
3549                    The names/ratings are sorted out when the game
3550                    actually starts (below).
3551                 */
3552                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3553                 player1Rating = string_to_rating(star_match[1]);
3554                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3555                 player2Rating = string_to_rating(star_match[4]);
3556
3557                 if (appData.debugMode)
3558                   fprintf(debugFP,
3559                           "Ratings from 'Creating:' %s %d, %s %d\n",
3560                           player1Name, player1Rating,
3561                           player2Name, player2Rating);
3562
3563                 continue;
3564             }
3565
3566             /* Improved generic start/end-of-game messages */
3567             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3568                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3569                 /* If tkind == 0: */
3570                 /* star_match[0] is the game number */
3571                 /*           [1] is the white player's name */
3572                 /*           [2] is the black player's name */
3573                 /* For end-of-game: */
3574                 /*           [3] is the reason for the game end */
3575                 /*           [4] is a PGN end game-token, preceded by " " */
3576                 /* For start-of-game: */
3577                 /*           [3] begins with "Creating" or "Continuing" */
3578                 /*           [4] is " *" or empty (don't care). */
3579                 int gamenum = atoi(star_match[0]);
3580                 char *whitename, *blackname, *why, *endtoken;
3581                 ChessMove endtype = EndOfFile;
3582
3583                 if (tkind == 0) {
3584                   whitename = star_match[1];
3585                   blackname = star_match[2];
3586                   why = star_match[3];
3587                   endtoken = star_match[4];
3588                 } else {
3589                   whitename = star_match[1];
3590                   blackname = star_match[3];
3591                   why = star_match[5];
3592                   endtoken = star_match[6];
3593                 }
3594
3595                 /* Game start messages */
3596                 if (strncmp(why, "Creating ", 9) == 0 ||
3597                     strncmp(why, "Continuing ", 11) == 0) {
3598                     gs_gamenum = gamenum;
3599                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3600                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3601 #if ZIPPY
3602                     if (appData.zippyPlay) {
3603                         ZippyGameStart(whitename, blackname);
3604                     }
3605 #endif /*ZIPPY*/
3606                     partnerBoardValid = FALSE; // [HGM] bughouse
3607                     continue;
3608                 }
3609
3610                 /* Game end messages */
3611                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3612                     ics_gamenum != gamenum) {
3613                     continue;
3614                 }
3615                 while (endtoken[0] == ' ') endtoken++;
3616                 switch (endtoken[0]) {
3617                   case '*':
3618                   default:
3619                     endtype = GameUnfinished;
3620                     break;
3621                   case '0':
3622                     endtype = BlackWins;
3623                     break;
3624                   case '1':
3625                     if (endtoken[1] == '/')
3626                       endtype = GameIsDrawn;
3627                     else
3628                       endtype = WhiteWins;
3629                     break;
3630                 }
3631                 GameEnds(endtype, why, GE_ICS);
3632 #if ZIPPY
3633                 if (appData.zippyPlay && first.initDone) {
3634                     ZippyGameEnd(endtype, why);
3635                     if (first.pr == NULL) {
3636                       /* Start the next process early so that we'll
3637                          be ready for the next challenge */
3638                       StartChessProgram(&first);
3639                     }
3640                     /* Send "new" early, in case this command takes
3641                        a long time to finish, so that we'll be ready
3642                        for the next challenge. */
3643                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3644                     Reset(TRUE, TRUE);
3645                 }
3646 #endif /*ZIPPY*/
3647                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3648                 continue;
3649             }
3650
3651             if (looking_at(buf, &i, "Removing game * from observation") ||
3652                 looking_at(buf, &i, "no longer observing game *") ||
3653                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3654                 if (gameMode == IcsObserving &&
3655                     atoi(star_match[0]) == ics_gamenum)
3656                   {
3657                       /* icsEngineAnalyze */
3658                       if (appData.icsEngineAnalyze) {
3659                             ExitAnalyzeMode();
3660                             ModeHighlight();
3661                       }
3662                       StopClocks();
3663                       gameMode = IcsIdle;
3664                       ics_gamenum = -1;
3665                       ics_user_moved = FALSE;
3666                   }
3667                 continue;
3668             }
3669
3670             if (looking_at(buf, &i, "no longer examining game *")) {
3671                 if (gameMode == IcsExamining &&
3672                     atoi(star_match[0]) == ics_gamenum)
3673                   {
3674                       gameMode = IcsIdle;
3675                       ics_gamenum = -1;
3676                       ics_user_moved = FALSE;
3677                   }
3678                 continue;
3679             }
3680
3681             /* Advance leftover_start past any newlines we find,
3682                so only partial lines can get reparsed */
3683             if (looking_at(buf, &i, "\n")) {
3684                 prevColor = curColor;
3685                 if (curColor != ColorNormal) {
3686                     if (oldi > next_out) {
3687                         SendToPlayer(&buf[next_out], oldi - next_out);
3688                         next_out = oldi;
3689                     }
3690                     Colorize(ColorNormal, FALSE);
3691                     curColor = ColorNormal;
3692                 }
3693                 if (started == STARTED_BOARD) {
3694                     started = STARTED_NONE;
3695                     parse[parse_pos] = NULLCHAR;
3696                     ParseBoard12(parse);
3697                     ics_user_moved = 0;
3698
3699                     /* Send premove here */
3700                     if (appData.premove) {
3701                       char str[MSG_SIZ];
3702                       if (currentMove == 0 &&
3703                           gameMode == IcsPlayingWhite &&
3704                           appData.premoveWhite) {
3705                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3706                         if (appData.debugMode)
3707                           fprintf(debugFP, "Sending premove:\n");
3708                         SendToICS(str);
3709                       } else if (currentMove == 1 &&
3710                                  gameMode == IcsPlayingBlack &&
3711                                  appData.premoveBlack) {
3712                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3713                         if (appData.debugMode)
3714                           fprintf(debugFP, "Sending premove:\n");
3715                         SendToICS(str);
3716                       } else if (gotPremove) {
3717                         gotPremove = 0;
3718                         ClearPremoveHighlights();
3719                         if (appData.debugMode)
3720                           fprintf(debugFP, "Sending premove:\n");
3721                           UserMoveEvent(premoveFromX, premoveFromY,
3722                                         premoveToX, premoveToY,
3723                                         premovePromoChar);
3724                       }
3725                     }
3726
3727                     /* Usually suppress following prompt */
3728                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3729                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3730                         if (looking_at(buf, &i, "*% ")) {
3731                             savingComment = FALSE;
3732                             suppressKibitz = 0;
3733                         }
3734                     }
3735                     next_out = i;
3736                 } else if (started == STARTED_HOLDINGS) {
3737                     int gamenum;
3738                     char new_piece[MSG_SIZ];
3739                     started = STARTED_NONE;
3740                     parse[parse_pos] = NULLCHAR;
3741                     if (appData.debugMode)
3742                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3743                                                         parse, currentMove);
3744                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3745                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3746                         if (gameInfo.variant == VariantNormal) {
3747                           /* [HGM] We seem to switch variant during a game!
3748                            * Presumably no holdings were displayed, so we have
3749                            * to move the position two files to the right to
3750                            * create room for them!
3751                            */
3752                           VariantClass newVariant;
3753                           switch(gameInfo.boardWidth) { // base guess on board width
3754                                 case 9:  newVariant = VariantShogi; break;
3755                                 case 10: newVariant = VariantGreat; break;
3756                                 default: newVariant = VariantCrazyhouse; break;
3757                           }
3758                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3759                           /* Get a move list just to see the header, which
3760                              will tell us whether this is really bug or zh */
3761                           if (ics_getting_history == H_FALSE) {
3762                             ics_getting_history = H_REQUESTED;
3763                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3764                             SendToICS(str);
3765                           }
3766                         }
3767                         new_piece[0] = NULLCHAR;
3768                         sscanf(parse, "game %d white [%s black [%s <- %s",
3769                                &gamenum, white_holding, black_holding,
3770                                new_piece);
3771                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3772                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3773                         /* [HGM] copy holdings to board holdings area */
3774                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3775                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3776                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3777 #if ZIPPY
3778                         if (appData.zippyPlay && first.initDone) {
3779                             ZippyHoldings(white_holding, black_holding,
3780                                           new_piece);
3781                         }
3782 #endif /*ZIPPY*/
3783                         if (tinyLayout || smallLayout) {
3784                             char wh[16], bh[16];
3785                             PackHolding(wh, white_holding);
3786                             PackHolding(bh, black_holding);
3787                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3788                                     gameInfo.white, gameInfo.black);
3789                         } else {
3790                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3791                                     gameInfo.white, white_holding,
3792                                     gameInfo.black, black_holding);
3793                         }
3794                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3795                         DrawPosition(FALSE, boards[currentMove]);
3796                         DisplayTitle(str);
3797                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3798                         sscanf(parse, "game %d white [%s black [%s <- %s",
3799                                &gamenum, white_holding, black_holding,
3800                                new_piece);
3801                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3802                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3803                         /* [HGM] copy holdings to partner-board holdings area */
3804                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3805                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3806                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3807                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3808                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3809                       }
3810                     }
3811                     /* Suppress following prompt */
3812                     if (looking_at(buf, &i, "*% ")) {
3813                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3814                         savingComment = FALSE;
3815                         suppressKibitz = 0;
3816                     }
3817                     next_out = i;
3818                 }
3819                 continue;
3820             }
3821
3822             i++;                /* skip unparsed character and loop back */
3823         }
3824
3825         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3826 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3827 //          SendToPlayer(&buf[next_out], i - next_out);
3828             started != STARTED_HOLDINGS && leftover_start > next_out) {
3829             SendToPlayer(&buf[next_out], leftover_start - next_out);
3830             next_out = i;
3831         }
3832
3833         leftover_len = buf_len - leftover_start;
3834         /* if buffer ends with something we couldn't parse,
3835            reparse it after appending the next read */
3836
3837     } else if (count == 0) {
3838         RemoveInputSource(isr);
3839         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3840     } else {
3841         DisplayFatalError(_("Error reading from ICS"), error, 1);
3842     }
3843 }
3844
3845
3846 /* Board style 12 looks like this:
3847
3848    <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
3849
3850  * The "<12> " is stripped before it gets to this routine.  The two
3851  * trailing 0's (flip state and clock ticking) are later addition, and
3852  * some chess servers may not have them, or may have only the first.
3853  * Additional trailing fields may be added in the future.
3854  */
3855
3856 #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"
3857
3858 #define RELATION_OBSERVING_PLAYED    0
3859 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3860 #define RELATION_PLAYING_MYMOVE      1
3861 #define RELATION_PLAYING_NOTMYMOVE  -1
3862 #define RELATION_EXAMINING           2
3863 #define RELATION_ISOLATED_BOARD     -3
3864 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3865
3866 void
3867 ParseBoard12(string)
3868      char *string;
3869 {
3870     GameMode newGameMode;
3871     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3872     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3873     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3874     char to_play, board_chars[200];
3875     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3876     char black[32], white[32];
3877     Board board;
3878     int prevMove = currentMove;
3879     int ticking = 2;
3880     ChessMove moveType;
3881     int fromX, fromY, toX, toY;
3882     char promoChar;
3883     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3884     char *bookHit = NULL; // [HGM] book
3885     Boolean weird = FALSE, reqFlag = FALSE;
3886
3887     fromX = fromY = toX = toY = -1;
3888
3889     newGame = FALSE;
3890
3891     if (appData.debugMode)
3892       fprintf(debugFP, _("Parsing board: %s\n"), string);
3893
3894     move_str[0] = NULLCHAR;
3895     elapsed_time[0] = NULLCHAR;
3896     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3897         int  i = 0, j;
3898         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3899             if(string[i] == ' ') { ranks++; files = 0; }
3900             else files++;
3901             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3902             i++;
3903         }
3904         for(j = 0; j <i; j++) board_chars[j] = string[j];
3905         board_chars[i] = '\0';
3906         string += i + 1;
3907     }
3908     n = sscanf(string, PATTERN, &to_play, &double_push,
3909                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3910                &gamenum, white, black, &relation, &basetime, &increment,
3911                &white_stren, &black_stren, &white_time, &black_time,
3912                &moveNum, str, elapsed_time, move_str, &ics_flip,
3913                &ticking);
3914
3915     if (n < 21) {
3916         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3917         DisplayError(str, 0);
3918         return;
3919     }
3920
3921     /* Convert the move number to internal form */
3922     moveNum = (moveNum - 1) * 2;
3923     if (to_play == 'B') moveNum++;
3924     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3925       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3926                         0, 1);
3927       return;
3928     }
3929
3930     switch (relation) {
3931       case RELATION_OBSERVING_PLAYED:
3932       case RELATION_OBSERVING_STATIC:
3933         if (gamenum == -1) {
3934             /* Old ICC buglet */
3935             relation = RELATION_OBSERVING_STATIC;
3936         }
3937         newGameMode = IcsObserving;
3938         break;
3939       case RELATION_PLAYING_MYMOVE:
3940       case RELATION_PLAYING_NOTMYMOVE:
3941         newGameMode =
3942           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3943             IcsPlayingWhite : IcsPlayingBlack;
3944         break;
3945       case RELATION_EXAMINING:
3946         newGameMode = IcsExamining;
3947         break;
3948       case RELATION_ISOLATED_BOARD:
3949       default:
3950         /* Just display this board.  If user was doing something else,
3951            we will forget about it until the next board comes. */
3952         newGameMode = IcsIdle;
3953         break;
3954       case RELATION_STARTING_POSITION:
3955         newGameMode = gameMode;
3956         break;
3957     }
3958
3959     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3960          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3961       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3962       char *toSqr;
3963       for (k = 0; k < ranks; k++) {
3964         for (j = 0; j < files; j++)
3965           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3966         if(gameInfo.holdingsWidth > 1) {
3967              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3968              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3969         }
3970       }
3971       CopyBoard(partnerBoard, board);
3972       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3973         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3974         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3975       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3976       if(toSqr = strchr(str, '-')) {
3977         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3978         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3979       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3980       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3981       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3982       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3983       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3984       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3985                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3986       DisplayMessage(partnerStatus, "");
3987         partnerBoardValid = TRUE;
3988       return;
3989     }
3990
3991     /* Modify behavior for initial board display on move listing
3992        of wild games.
3993        */
3994     switch (ics_getting_history) {
3995       case H_FALSE:
3996       case H_REQUESTED:
3997         break;
3998       case H_GOT_REQ_HEADER:
3999       case H_GOT_UNREQ_HEADER:
4000         /* This is the initial position of the current game */
4001         gamenum = ics_gamenum;
4002         moveNum = 0;            /* old ICS bug workaround */
4003         if (to_play == 'B') {
4004           startedFromSetupPosition = TRUE;
4005           blackPlaysFirst = TRUE;
4006           moveNum = 1;
4007           if (forwardMostMove == 0) forwardMostMove = 1;
4008           if (backwardMostMove == 0) backwardMostMove = 1;
4009           if (currentMove == 0) currentMove = 1;
4010         }
4011         newGameMode = gameMode;
4012         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4013         break;
4014       case H_GOT_UNWANTED_HEADER:
4015         /* This is an initial board that we don't want */
4016         return;
4017       case H_GETTING_MOVES:
4018         /* Should not happen */
4019         DisplayError(_("Error gathering move list: extra board"), 0);
4020         ics_getting_history = H_FALSE;
4021         return;
4022     }
4023
4024    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4025                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4026      /* [HGM] We seem to have switched variant unexpectedly
4027       * Try to guess new variant from board size
4028       */
4029           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4030           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4031           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4032           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4033           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4034           if(!weird) newVariant = VariantNormal;
4035           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4036           /* Get a move list just to see the header, which
4037              will tell us whether this is really bug or zh */
4038           if (ics_getting_history == H_FALSE) {
4039             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4040             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4041             SendToICS(str);
4042           }
4043     }
4044
4045     /* Take action if this is the first board of a new game, or of a
4046        different game than is currently being displayed.  */
4047     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4048         relation == RELATION_ISOLATED_BOARD) {
4049
4050         /* Forget the old game and get the history (if any) of the new one */
4051         if (gameMode != BeginningOfGame) {
4052           Reset(TRUE, TRUE);
4053         }
4054         newGame = TRUE;
4055         if (appData.autoRaiseBoard) BoardToTop();
4056         prevMove = -3;
4057         if (gamenum == -1) {
4058             newGameMode = IcsIdle;
4059         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4060                    appData.getMoveList && !reqFlag) {
4061             /* Need to get game history */
4062             ics_getting_history = H_REQUESTED;
4063             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4064             SendToICS(str);
4065         }
4066
4067         /* Initially flip the board to have black on the bottom if playing
4068            black or if the ICS flip flag is set, but let the user change
4069            it with the Flip View button. */
4070         flipView = appData.autoFlipView ?
4071           (newGameMode == IcsPlayingBlack) || ics_flip :
4072           appData.flipView;
4073
4074         /* Done with values from previous mode; copy in new ones */
4075         gameMode = newGameMode;
4076         ModeHighlight();
4077         ics_gamenum = gamenum;
4078         if (gamenum == gs_gamenum) {
4079             int klen = strlen(gs_kind);
4080             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4081             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4082             gameInfo.event = StrSave(str);
4083         } else {
4084             gameInfo.event = StrSave("ICS game");
4085         }
4086         gameInfo.site = StrSave(appData.icsHost);
4087         gameInfo.date = PGNDate();
4088         gameInfo.round = StrSave("-");
4089         gameInfo.white = StrSave(white);
4090         gameInfo.black = StrSave(black);
4091         timeControl = basetime * 60 * 1000;
4092         timeControl_2 = 0;
4093         timeIncrement = increment * 1000;
4094         movesPerSession = 0;
4095         gameInfo.timeControl = TimeControlTagValue();
4096         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4097   if (appData.debugMode) {
4098     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4099     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4100     setbuf(debugFP, NULL);
4101   }
4102
4103         gameInfo.outOfBook = NULL;
4104
4105         /* Do we have the ratings? */
4106         if (strcmp(player1Name, white) == 0 &&
4107             strcmp(player2Name, black) == 0) {
4108             if (appData.debugMode)
4109               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4110                       player1Rating, player2Rating);
4111             gameInfo.whiteRating = player1Rating;
4112             gameInfo.blackRating = player2Rating;
4113         } else if (strcmp(player2Name, white) == 0 &&
4114                    strcmp(player1Name, black) == 0) {
4115             if (appData.debugMode)
4116               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4117                       player2Rating, player1Rating);
4118             gameInfo.whiteRating = player2Rating;
4119             gameInfo.blackRating = player1Rating;
4120         }
4121         player1Name[0] = player2Name[0] = NULLCHAR;
4122
4123         /* Silence shouts if requested */
4124         if (appData.quietPlay &&
4125             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4126             SendToICS(ics_prefix);
4127             SendToICS("set shout 0\n");
4128         }
4129     }
4130
4131     /* Deal with midgame name changes */
4132     if (!newGame) {
4133         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4134             if (gameInfo.white) free(gameInfo.white);
4135             gameInfo.white = StrSave(white);
4136         }
4137         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4138             if (gameInfo.black) free(gameInfo.black);
4139             gameInfo.black = StrSave(black);
4140         }
4141     }
4142
4143     /* Throw away game result if anything actually changes in examine mode */
4144     if (gameMode == IcsExamining && !newGame) {
4145         gameInfo.result = GameUnfinished;
4146         if (gameInfo.resultDetails != NULL) {
4147             free(gameInfo.resultDetails);
4148             gameInfo.resultDetails = NULL;
4149         }
4150     }
4151
4152     /* In pausing && IcsExamining mode, we ignore boards coming
4153        in if they are in a different variation than we are. */
4154     if (pauseExamInvalid) return;
4155     if (pausing && gameMode == IcsExamining) {
4156         if (moveNum <= pauseExamForwardMostMove) {
4157             pauseExamInvalid = TRUE;
4158             forwardMostMove = pauseExamForwardMostMove;
4159             return;
4160         }
4161     }
4162
4163   if (appData.debugMode) {
4164     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4165   }
4166     /* Parse the board */
4167     for (k = 0; k < ranks; k++) {
4168       for (j = 0; j < files; j++)
4169         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4170       if(gameInfo.holdingsWidth > 1) {
4171            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4172            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4173       }
4174     }
4175     CopyBoard(boards[moveNum], board);
4176     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4177     if (moveNum == 0) {
4178         startedFromSetupPosition =
4179           !CompareBoards(board, initialPosition);
4180         if(startedFromSetupPosition)
4181             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4182     }
4183
4184     /* [HGM] Set castling rights. Take the outermost Rooks,
4185        to make it also work for FRC opening positions. Note that board12
4186        is really defective for later FRC positions, as it has no way to
4187        indicate which Rook can castle if they are on the same side of King.
4188        For the initial position we grant rights to the outermost Rooks,
4189        and remember thos rights, and we then copy them on positions
4190        later in an FRC game. This means WB might not recognize castlings with
4191        Rooks that have moved back to their original position as illegal,
4192        but in ICS mode that is not its job anyway.
4193     */
4194     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4195     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4196
4197         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4198             if(board[0][i] == WhiteRook) j = i;
4199         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4200         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4201             if(board[0][i] == WhiteRook) j = i;
4202         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4203         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4204             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4205         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4206         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4207             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4208         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4209
4210         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4211         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4212             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4213         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4214             if(board[BOARD_HEIGHT-1][k] == bKing)
4215                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4216         if(gameInfo.variant == VariantTwoKings) {
4217             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4218             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4219             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4220         }
4221     } else { int r;
4222         r = boards[moveNum][CASTLING][0] = initialRights[0];
4223         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4224         r = boards[moveNum][CASTLING][1] = initialRights[1];
4225         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4226         r = boards[moveNum][CASTLING][3] = initialRights[3];
4227         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4228         r = boards[moveNum][CASTLING][4] = initialRights[4];
4229         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4230         /* wildcastle kludge: always assume King has rights */
4231         r = boards[moveNum][CASTLING][2] = initialRights[2];
4232         r = boards[moveNum][CASTLING][5] = initialRights[5];
4233     }
4234     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4235     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4236
4237
4238     if (ics_getting_history == H_GOT_REQ_HEADER ||
4239         ics_getting_history == H_GOT_UNREQ_HEADER) {
4240         /* This was an initial position from a move list, not
4241            the current position */
4242         return;
4243     }
4244
4245     /* Update currentMove and known move number limits */
4246     newMove = newGame || moveNum > forwardMostMove;
4247
4248     if (newGame) {
4249         forwardMostMove = backwardMostMove = currentMove = moveNum;
4250         if (gameMode == IcsExamining && moveNum == 0) {
4251           /* Workaround for ICS limitation: we are not told the wild
4252              type when starting to examine a game.  But if we ask for
4253              the move list, the move list header will tell us */
4254             ics_getting_history = H_REQUESTED;
4255             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4256             SendToICS(str);
4257         }
4258     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4259                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4260 #if ZIPPY
4261         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4262         /* [HGM] applied this also to an engine that is silently watching        */
4263         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4264             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4265             gameInfo.variant == currentlyInitializedVariant) {
4266           takeback = forwardMostMove - moveNum;
4267           for (i = 0; i < takeback; i++) {
4268             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4269             SendToProgram("undo\n", &first);
4270           }
4271         }
4272 #endif
4273
4274         forwardMostMove = moveNum;
4275         if (!pausing || currentMove > forwardMostMove)
4276           currentMove = forwardMostMove;
4277     } else {
4278         /* New part of history that is not contiguous with old part */
4279         if (pausing && gameMode == IcsExamining) {
4280             pauseExamInvalid = TRUE;
4281             forwardMostMove = pauseExamForwardMostMove;
4282             return;
4283         }
4284         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4285 #if ZIPPY
4286             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4287                 // [HGM] when we will receive the move list we now request, it will be
4288                 // fed to the engine from the first move on. So if the engine is not
4289                 // in the initial position now, bring it there.
4290                 InitChessProgram(&first, 0);
4291             }
4292 #endif
4293             ics_getting_history = H_REQUESTED;
4294             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4295             SendToICS(str);
4296         }
4297         forwardMostMove = backwardMostMove = currentMove = moveNum;
4298     }
4299
4300     /* Update the clocks */
4301     if (strchr(elapsed_time, '.')) {
4302       /* Time is in ms */
4303       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4304       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4305     } else {
4306       /* Time is in seconds */
4307       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4308       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4309     }
4310
4311
4312 #if ZIPPY
4313     if (appData.zippyPlay && newGame &&
4314         gameMode != IcsObserving && gameMode != IcsIdle &&
4315         gameMode != IcsExamining)
4316       ZippyFirstBoard(moveNum, basetime, increment);
4317 #endif
4318
4319     /* Put the move on the move list, first converting
4320        to canonical algebraic form. */
4321     if (moveNum > 0) {
4322   if (appData.debugMode) {
4323     if (appData.debugMode) { int f = forwardMostMove;
4324         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4325                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4326                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4327     }
4328     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4329     fprintf(debugFP, "moveNum = %d\n", moveNum);
4330     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4331     setbuf(debugFP, NULL);
4332   }
4333         if (moveNum <= backwardMostMove) {
4334             /* We don't know what the board looked like before
4335                this move.  Punt. */
4336           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4337             strcat(parseList[moveNum - 1], " ");
4338             strcat(parseList[moveNum - 1], elapsed_time);
4339             moveList[moveNum - 1][0] = NULLCHAR;
4340         } else if (strcmp(move_str, "none") == 0) {
4341             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4342             /* Again, we don't know what the board looked like;
4343                this is really the start of the game. */
4344             parseList[moveNum - 1][0] = NULLCHAR;
4345             moveList[moveNum - 1][0] = NULLCHAR;
4346             backwardMostMove = moveNum;
4347             startedFromSetupPosition = TRUE;
4348             fromX = fromY = toX = toY = -1;
4349         } else {
4350           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4351           //                 So we parse the long-algebraic move string in stead of the SAN move
4352           int valid; char buf[MSG_SIZ], *prom;
4353
4354           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4355                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4356           // str looks something like "Q/a1-a2"; kill the slash
4357           if(str[1] == '/')
4358             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4359           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4360           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4361                 strcat(buf, prom); // long move lacks promo specification!
4362           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4363                 if(appData.debugMode)
4364                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4365                 safeStrCpy(move_str, buf, MSG_SIZ);
4366           }
4367           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4368                                 &fromX, &fromY, &toX, &toY, &promoChar)
4369                || ParseOneMove(buf, moveNum - 1, &moveType,
4370                                 &fromX, &fromY, &toX, &toY, &promoChar);
4371           // end of long SAN patch
4372           if (valid) {
4373             (void) CoordsToAlgebraic(boards[moveNum - 1],
4374                                      PosFlags(moveNum - 1),
4375                                      fromY, fromX, toY, toX, promoChar,
4376                                      parseList[moveNum-1]);
4377             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4378               case MT_NONE:
4379               case MT_STALEMATE:
4380               default:
4381                 break;
4382               case MT_CHECK:
4383                 if(gameInfo.variant != VariantShogi)
4384                     strcat(parseList[moveNum - 1], "+");
4385                 break;
4386               case MT_CHECKMATE:
4387               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4388                 strcat(parseList[moveNum - 1], "#");
4389                 break;
4390             }
4391             strcat(parseList[moveNum - 1], " ");
4392             strcat(parseList[moveNum - 1], elapsed_time);
4393             /* currentMoveString is set as a side-effect of ParseOneMove */
4394             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4395             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4396             strcat(moveList[moveNum - 1], "\n");
4397
4398             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4399                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4400               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4401                 ChessSquare old, new = boards[moveNum][k][j];
4402                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4403                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4404                   if(old == new) continue;
4405                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4406                   else if(new == WhiteWazir || new == BlackWazir) {
4407                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4408                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4409                       else boards[moveNum][k][j] = old; // preserve type of Gold
4410                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4411                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4412               }
4413           } else {
4414             /* Move from ICS was illegal!?  Punt. */
4415             if (appData.debugMode) {
4416               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4417               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4418             }
4419             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4420             strcat(parseList[moveNum - 1], " ");
4421             strcat(parseList[moveNum - 1], elapsed_time);
4422             moveList[moveNum - 1][0] = NULLCHAR;
4423             fromX = fromY = toX = toY = -1;
4424           }
4425         }
4426   if (appData.debugMode) {
4427     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4428     setbuf(debugFP, NULL);
4429   }
4430
4431 #if ZIPPY
4432         /* Send move to chess program (BEFORE animating it). */
4433         if (appData.zippyPlay && !newGame && newMove &&
4434            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4435
4436             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4437                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4438                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4439                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4440                             move_str);
4441                     DisplayError(str, 0);
4442                 } else {
4443                     if (first.sendTime) {
4444                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4445                     }
4446                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4447                     if (firstMove && !bookHit) {
4448                         firstMove = FALSE;
4449                         if (first.useColors) {
4450                           SendToProgram(gameMode == IcsPlayingWhite ?
4451                                         "white\ngo\n" :
4452                                         "black\ngo\n", &first);
4453                         } else {
4454                           SendToProgram("go\n", &first);
4455                         }
4456                         first.maybeThinking = TRUE;
4457                     }
4458                 }
4459             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4460               if (moveList[moveNum - 1][0] == NULLCHAR) {
4461                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4462                 DisplayError(str, 0);
4463               } else {
4464                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4465                 SendMoveToProgram(moveNum - 1, &first);
4466               }
4467             }
4468         }
4469 #endif
4470     }
4471
4472     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4473         /* If move comes from a remote source, animate it.  If it
4474            isn't remote, it will have already been animated. */
4475         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4476             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4477         }
4478         if (!pausing && appData.highlightLastMove) {
4479             SetHighlights(fromX, fromY, toX, toY);
4480         }
4481     }
4482
4483     /* Start the clocks */
4484     whiteFlag = blackFlag = FALSE;
4485     appData.clockMode = !(basetime == 0 && increment == 0);
4486     if (ticking == 0) {
4487       ics_clock_paused = TRUE;
4488       StopClocks();
4489     } else if (ticking == 1) {
4490       ics_clock_paused = FALSE;
4491     }
4492     if (gameMode == IcsIdle ||
4493         relation == RELATION_OBSERVING_STATIC ||
4494         relation == RELATION_EXAMINING ||
4495         ics_clock_paused)
4496       DisplayBothClocks();
4497     else
4498       StartClocks();
4499
4500     /* Display opponents and material strengths */
4501     if (gameInfo.variant != VariantBughouse &&
4502         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4503         if (tinyLayout || smallLayout) {
4504             if(gameInfo.variant == VariantNormal)
4505               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4506                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4507                     basetime, increment);
4508             else
4509               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4510                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4511                     basetime, increment, (int) gameInfo.variant);
4512         } else {
4513             if(gameInfo.variant == VariantNormal)
4514               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4515                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4516                     basetime, increment);
4517             else
4518               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4519                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4520                     basetime, increment, VariantName(gameInfo.variant));
4521         }
4522         DisplayTitle(str);
4523   if (appData.debugMode) {
4524     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4525   }
4526     }
4527
4528
4529     /* Display the board */
4530     if (!pausing && !appData.noGUI) {
4531
4532       if (appData.premove)
4533           if (!gotPremove ||
4534              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4535              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4536               ClearPremoveHighlights();
4537
4538       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4539         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4540       DrawPosition(j, boards[currentMove]);
4541
4542       DisplayMove(moveNum - 1);
4543       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4544             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4545               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4546         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4547       }
4548     }
4549
4550     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4551 #if ZIPPY
4552     if(bookHit) { // [HGM] book: simulate book reply
4553         static char bookMove[MSG_SIZ]; // a bit generous?
4554
4555         programStats.nodes = programStats.depth = programStats.time =
4556         programStats.score = programStats.got_only_move = 0;
4557         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4558
4559         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4560         strcat(bookMove, bookHit);
4561         HandleMachineMove(bookMove, &first);
4562     }
4563 #endif
4564 }
4565
4566 void
4567 GetMoveListEvent()
4568 {
4569     char buf[MSG_SIZ];
4570     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4571         ics_getting_history = H_REQUESTED;
4572         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4573         SendToICS(buf);
4574     }
4575 }
4576
4577 void
4578 AnalysisPeriodicEvent(force)
4579      int force;
4580 {
4581     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4582          && !force) || !appData.periodicUpdates)
4583       return;
4584
4585     /* Send . command to Crafty to collect stats */
4586     SendToProgram(".\n", &first);
4587
4588     /* Don't send another until we get a response (this makes
4589        us stop sending to old Crafty's which don't understand
4590        the "." command (sending illegal cmds resets node count & time,
4591        which looks bad)) */
4592     programStats.ok_to_send = 0;
4593 }
4594
4595 void ics_update_width(new_width)
4596         int new_width;
4597 {
4598         ics_printf("set width %d\n", new_width);
4599 }
4600
4601 void
4602 SendMoveToProgram(moveNum, cps)
4603      int moveNum;
4604      ChessProgramState *cps;
4605 {
4606     char buf[MSG_SIZ];
4607
4608     if (cps->useUsermove) {
4609       SendToProgram("usermove ", cps);
4610     }
4611     if (cps->useSAN) {
4612       char *space;
4613       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4614         int len = space - parseList[moveNum];
4615         memcpy(buf, parseList[moveNum], len);
4616         buf[len++] = '\n';
4617         buf[len] = NULLCHAR;
4618       } else {
4619         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4620       }
4621       SendToProgram(buf, cps);
4622     } else {
4623       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4624         AlphaRank(moveList[moveNum], 4);
4625         SendToProgram(moveList[moveNum], cps);
4626         AlphaRank(moveList[moveNum], 4); // and back
4627       } else
4628       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4629        * the engine. It would be nice to have a better way to identify castle
4630        * moves here. */
4631       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4632                                                                          && cps->useOOCastle) {
4633         int fromX = moveList[moveNum][0] - AAA;
4634         int fromY = moveList[moveNum][1] - ONE;
4635         int toX = moveList[moveNum][2] - AAA;
4636         int toY = moveList[moveNum][3] - ONE;
4637         if((boards[moveNum][fromY][fromX] == WhiteKing
4638             && boards[moveNum][toY][toX] == WhiteRook)
4639            || (boards[moveNum][fromY][fromX] == BlackKing
4640                && boards[moveNum][toY][toX] == BlackRook)) {
4641           if(toX > fromX) SendToProgram("O-O\n", cps);
4642           else SendToProgram("O-O-O\n", cps);
4643         }
4644         else SendToProgram(moveList[moveNum], cps);
4645       }
4646       else SendToProgram(moveList[moveNum], cps);
4647       /* End of additions by Tord */
4648     }
4649
4650     /* [HGM] setting up the opening has brought engine in force mode! */
4651     /*       Send 'go' if we are in a mode where machine should play. */
4652     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4653         (gameMode == TwoMachinesPlay   ||
4654 #if ZIPPY
4655          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4656 #endif
4657          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4658         SendToProgram("go\n", cps);
4659   if (appData.debugMode) {
4660     fprintf(debugFP, "(extra)\n");
4661   }
4662     }
4663     setboardSpoiledMachineBlack = 0;
4664 }
4665
4666 void
4667 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4668      ChessMove moveType;
4669      int fromX, fromY, toX, toY;
4670      char promoChar;
4671 {
4672     char user_move[MSG_SIZ];
4673
4674     switch (moveType) {
4675       default:
4676         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4677                 (int)moveType, fromX, fromY, toX, toY);
4678         DisplayError(user_move + strlen("say "), 0);
4679         break;
4680       case WhiteKingSideCastle:
4681       case BlackKingSideCastle:
4682       case WhiteQueenSideCastleWild:
4683       case BlackQueenSideCastleWild:
4684       /* PUSH Fabien */
4685       case WhiteHSideCastleFR:
4686       case BlackHSideCastleFR:
4687       /* POP Fabien */
4688         snprintf(user_move, MSG_SIZ, "o-o\n");
4689         break;
4690       case WhiteQueenSideCastle:
4691       case BlackQueenSideCastle:
4692       case WhiteKingSideCastleWild:
4693       case BlackKingSideCastleWild:
4694       /* PUSH Fabien */
4695       case WhiteASideCastleFR:
4696       case BlackASideCastleFR:
4697       /* POP Fabien */
4698         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4699         break;
4700       case WhiteNonPromotion:
4701       case BlackNonPromotion:
4702         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4703         break;
4704       case WhitePromotion:
4705       case BlackPromotion:
4706         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4707           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4708                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4709                 PieceToChar(WhiteFerz));
4710         else if(gameInfo.variant == VariantGreat)
4711           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4712                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4713                 PieceToChar(WhiteMan));
4714         else
4715           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4716                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4717                 promoChar);
4718         break;
4719       case WhiteDrop:
4720       case BlackDrop:
4721       drop:
4722         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4723                  ToUpper(PieceToChar((ChessSquare) fromX)),
4724                  AAA + toX, ONE + toY);
4725         break;
4726       case IllegalMove:  /* could be a variant we don't quite understand */
4727         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4728       case NormalMove:
4729       case WhiteCapturesEnPassant:
4730       case BlackCapturesEnPassant:
4731         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4732                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4733         break;
4734     }
4735     SendToICS(user_move);
4736     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4737         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4738 }
4739
4740 void
4741 UploadGameEvent()
4742 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4743     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4744     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4745     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4746         DisplayError("You cannot do this while you are playing or observing", 0);
4747         return;
4748     }
4749     if(gameMode != IcsExamining) { // is this ever not the case?
4750         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4751
4752         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4753           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4754         } else { // on FICS we must first go to general examine mode
4755           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4756         }
4757         if(gameInfo.variant != VariantNormal) {
4758             // try figure out wild number, as xboard names are not always valid on ICS
4759             for(i=1; i<=36; i++) {
4760               snprintf(buf, MSG_SIZ, "wild/%d", i);
4761                 if(StringToVariant(buf) == gameInfo.variant) break;
4762             }
4763             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4764             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4765             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4766         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4767         SendToICS(ics_prefix);
4768         SendToICS(buf);
4769         if(startedFromSetupPosition || backwardMostMove != 0) {
4770           fen = PositionToFEN(backwardMostMove, NULL);
4771           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4772             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4773             SendToICS(buf);
4774           } else { // FICS: everything has to set by separate bsetup commands
4775             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4776             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4777             SendToICS(buf);
4778             if(!WhiteOnMove(backwardMostMove)) {
4779                 SendToICS("bsetup tomove black\n");
4780             }
4781             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4782             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4783             SendToICS(buf);
4784             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4785             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4786             SendToICS(buf);
4787             i = boards[backwardMostMove][EP_STATUS];
4788             if(i >= 0) { // set e.p.
4789               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4790                 SendToICS(buf);
4791             }
4792             bsetup++;
4793           }
4794         }
4795       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4796             SendToICS("bsetup done\n"); // switch to normal examining.
4797     }
4798     for(i = backwardMostMove; i<last; i++) {
4799         char buf[20];
4800         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4801         SendToICS(buf);
4802     }
4803     SendToICS(ics_prefix);
4804     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4805 }
4806
4807 void
4808 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4809      int rf, ff, rt, ft;
4810      char promoChar;
4811      char move[7];
4812 {
4813     if (rf == DROP_RANK) {
4814       sprintf(move, "%c@%c%c\n",
4815                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4816     } else {
4817         if (promoChar == 'x' || promoChar == NULLCHAR) {
4818           sprintf(move, "%c%c%c%c\n",
4819                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4820         } else {
4821             sprintf(move, "%c%c%c%c%c\n",
4822                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4823         }
4824     }
4825 }
4826
4827 void
4828 ProcessICSInitScript(f)
4829      FILE *f;
4830 {
4831     char buf[MSG_SIZ];
4832
4833     while (fgets(buf, MSG_SIZ, f)) {
4834         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4835     }
4836
4837     fclose(f);
4838 }
4839
4840
4841 static int lastX, lastY, selectFlag, dragging;
4842
4843 void
4844 Sweep(int step)
4845 {
4846     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4847     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4848     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4849     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4850     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4851     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4852     do {
4853         promoSweep -= step;
4854         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4855         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4856         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4857         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4858         if(!step) step = 1;
4859     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4860             appData.testLegality && (promoSweep == king ||
4861             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4862     ChangeDragPiece(promoSweep);
4863 }
4864
4865 int PromoScroll(int x, int y)
4866 {
4867   int step = 0;
4868
4869   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4870   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4871   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4872   if(!step) return FALSE;
4873   lastX = x; lastY = y;
4874   if((promoSweep < BlackPawn) == flipView) step = -step;
4875   if(step > 0) selectFlag = 1;
4876   if(!selectFlag) Sweep(step);
4877   return FALSE;
4878 }
4879
4880 void
4881 NextPiece(int step)
4882 {
4883     ChessSquare piece = boards[currentMove][toY][toX];
4884     do {
4885         pieceSweep -= step;
4886         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4887         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4888         if(!step) step = -1;
4889     } while(PieceToChar(pieceSweep) == '.');
4890     boards[currentMove][toY][toX] = pieceSweep;
4891     DrawPosition(FALSE, boards[currentMove]);
4892     boards[currentMove][toY][toX] = piece;
4893 }
4894 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4895 void
4896 AlphaRank(char *move, int n)
4897 {
4898 //    char *p = move, c; int x, y;
4899
4900     if (appData.debugMode) {
4901         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4902     }
4903
4904     if(move[1]=='*' &&
4905        move[2]>='0' && move[2]<='9' &&
4906        move[3]>='a' && move[3]<='x'    ) {
4907         move[1] = '@';
4908         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4909         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4910     } else
4911     if(move[0]>='0' && move[0]<='9' &&
4912        move[1]>='a' && move[1]<='x' &&
4913        move[2]>='0' && move[2]<='9' &&
4914        move[3]>='a' && move[3]<='x'    ) {
4915         /* input move, Shogi -> normal */
4916         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4917         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4918         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4919         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4920     } else
4921     if(move[1]=='@' &&
4922        move[3]>='0' && move[3]<='9' &&
4923        move[2]>='a' && move[2]<='x'    ) {
4924         move[1] = '*';
4925         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4926         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4927     } else
4928     if(
4929        move[0]>='a' && move[0]<='x' &&
4930        move[3]>='0' && move[3]<='9' &&
4931        move[2]>='a' && move[2]<='x'    ) {
4932          /* output move, normal -> Shogi */
4933         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4934         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4935         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4936         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4937         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4938     }
4939     if (appData.debugMode) {
4940         fprintf(debugFP, "   out = '%s'\n", move);
4941     }
4942 }
4943
4944 char yy_textstr[8000];
4945
4946 /* Parser for moves from gnuchess, ICS, or user typein box */
4947 Boolean
4948 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4949      char *move;
4950      int moveNum;
4951      ChessMove *moveType;
4952      int *fromX, *fromY, *toX, *toY;
4953      char *promoChar;
4954 {
4955     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4956
4957     switch (*moveType) {
4958       case WhitePromotion:
4959       case BlackPromotion:
4960       case WhiteNonPromotion:
4961       case BlackNonPromotion:
4962       case NormalMove:
4963       case WhiteCapturesEnPassant:
4964       case BlackCapturesEnPassant:
4965       case WhiteKingSideCastle:
4966       case WhiteQueenSideCastle:
4967       case BlackKingSideCastle:
4968       case BlackQueenSideCastle:
4969       case WhiteKingSideCastleWild:
4970       case WhiteQueenSideCastleWild:
4971       case BlackKingSideCastleWild:
4972       case BlackQueenSideCastleWild:
4973       /* Code added by Tord: */
4974       case WhiteHSideCastleFR:
4975       case WhiteASideCastleFR:
4976       case BlackHSideCastleFR:
4977       case BlackASideCastleFR:
4978       /* End of code added by Tord */
4979       case IllegalMove:         /* bug or odd chess variant */
4980         *fromX = currentMoveString[0] - AAA;
4981         *fromY = currentMoveString[1] - ONE;
4982         *toX = currentMoveString[2] - AAA;
4983         *toY = currentMoveString[3] - ONE;
4984         *promoChar = currentMoveString[4];
4985         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4986             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4987     if (appData.debugMode) {
4988         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4989     }
4990             *fromX = *fromY = *toX = *toY = 0;
4991             return FALSE;
4992         }
4993         if (appData.testLegality) {
4994           return (*moveType != IllegalMove);
4995         } else {
4996           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4997                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4998         }
4999
5000       case WhiteDrop:
5001       case BlackDrop:
5002         *fromX = *moveType == WhiteDrop ?
5003           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5004           (int) CharToPiece(ToLower(currentMoveString[0]));
5005         *fromY = DROP_RANK;
5006         *toX = currentMoveString[2] - AAA;
5007         *toY = currentMoveString[3] - ONE;
5008         *promoChar = NULLCHAR;
5009         return TRUE;
5010
5011       case AmbiguousMove:
5012       case ImpossibleMove:
5013       case EndOfFile:
5014       case ElapsedTime:
5015       case Comment:
5016       case PGNTag:
5017       case NAG:
5018       case WhiteWins:
5019       case BlackWins:
5020       case GameIsDrawn:
5021       default:
5022     if (appData.debugMode) {
5023         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5024     }
5025         /* bug? */
5026         *fromX = *fromY = *toX = *toY = 0;
5027         *promoChar = NULLCHAR;
5028         return FALSE;
5029     }
5030 }
5031
5032
5033 void
5034 ParsePV(char *pv, Boolean storeComments)
5035 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5036   int fromX, fromY, toX, toY; char promoChar;
5037   ChessMove moveType;
5038   Boolean valid;
5039   int nr = 0;
5040
5041   endPV = forwardMostMove;
5042   do {
5043     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5044     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5045     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5046 if(appData.debugMode){
5047 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);
5048 }
5049     if(!valid && nr == 0 &&
5050        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5051         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5052         // Hande case where played move is different from leading PV move
5053         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5054         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5055         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5056         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5057           endPV += 2; // if position different, keep this
5058           moveList[endPV-1][0] = fromX + AAA;
5059           moveList[endPV-1][1] = fromY + ONE;
5060           moveList[endPV-1][2] = toX + AAA;
5061           moveList[endPV-1][3] = toY + ONE;
5062           parseList[endPV-1][0] = NULLCHAR;
5063           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5064         }
5065       }
5066     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5067     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5068     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5069     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5070         valid++; // allow comments in PV
5071         continue;
5072     }
5073     nr++;
5074     if(endPV+1 > framePtr) break; // no space, truncate
5075     if(!valid) break;
5076     endPV++;
5077     CopyBoard(boards[endPV], boards[endPV-1]);
5078     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5079     moveList[endPV-1][0] = fromX + AAA;
5080     moveList[endPV-1][1] = fromY + ONE;
5081     moveList[endPV-1][2] = toX + AAA;
5082     moveList[endPV-1][3] = toY + ONE;
5083     moveList[endPV-1][4] = promoChar;
5084     moveList[endPV-1][5] = NULLCHAR;
5085     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5086     if(storeComments)
5087         CoordsToAlgebraic(boards[endPV - 1],
5088                              PosFlags(endPV - 1),
5089                              fromY, fromX, toY, toX, promoChar,
5090                              parseList[endPV - 1]);
5091     else
5092         parseList[endPV-1][0] = NULLCHAR;
5093   } while(valid);
5094   currentMove = endPV;
5095   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5096   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5097                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5098   DrawPosition(TRUE, boards[currentMove]);
5099 }
5100
5101 Boolean
5102 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5103 {
5104         int startPV;
5105         char *p;
5106
5107         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5108         lastX = x; lastY = y;
5109         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5110         startPV = index;
5111         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5112         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5113         index = startPV;
5114         do{ while(buf[index] && buf[index] != '\n') index++;
5115         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5116         buf[index] = 0;
5117         ParsePV(buf+startPV, FALSE);
5118         *start = startPV; *end = index-1;
5119         return TRUE;
5120 }
5121
5122 Boolean
5123 LoadPV(int x, int y)
5124 { // called on right mouse click to load PV
5125   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5126   lastX = x; lastY = y;
5127   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5128   return TRUE;
5129 }
5130
5131 void
5132 UnLoadPV()
5133 {
5134   if(endPV < 0) return;
5135   endPV = -1;
5136   currentMove = forwardMostMove;
5137   ClearPremoveHighlights();
5138   DrawPosition(TRUE, boards[currentMove]);
5139 }
5140
5141 void
5142 MovePV(int x, int y, int h)
5143 { // step through PV based on mouse coordinates (called on mouse move)
5144   int margin = h>>3, step = 0;
5145
5146   // we must somehow check if right button is still down (might be released off board!)
5147   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5148   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5149   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5150   if(!step) return;
5151   lastX = x; lastY = y;
5152
5153   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5154   if(endPV < 0) return;
5155   if(y < margin) step = 1; else
5156   if(y > h - margin) step = -1;
5157   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5158   currentMove += step;
5159   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5160   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5161                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5162   DrawPosition(FALSE, boards[currentMove]);
5163 }
5164
5165
5166 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5167 // All positions will have equal probability, but the current method will not provide a unique
5168 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5169 #define DARK 1
5170 #define LITE 2
5171 #define ANY 3
5172
5173 int squaresLeft[4];
5174 int piecesLeft[(int)BlackPawn];
5175 int seed, nrOfShuffles;
5176
5177 void GetPositionNumber()
5178 {       // sets global variable seed
5179         int i;
5180
5181         seed = appData.defaultFrcPosition;
5182         if(seed < 0) { // randomize based on time for negative FRC position numbers
5183                 for(i=0; i<50; i++) seed += random();
5184                 seed = random() ^ random() >> 8 ^ random() << 8;
5185                 if(seed<0) seed = -seed;
5186         }
5187 }
5188
5189 int put(Board board, int pieceType, int rank, int n, int shade)
5190 // put the piece on the (n-1)-th empty squares of the given shade
5191 {
5192         int i;
5193
5194         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5195                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5196                         board[rank][i] = (ChessSquare) pieceType;
5197                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5198                         squaresLeft[ANY]--;
5199                         piecesLeft[pieceType]--;
5200                         return i;
5201                 }
5202         }
5203         return -1;
5204 }
5205
5206
5207 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5208 // calculate where the next piece goes, (any empty square), and put it there
5209 {
5210         int i;
5211
5212         i = seed % squaresLeft[shade];
5213         nrOfShuffles *= squaresLeft[shade];
5214         seed /= squaresLeft[shade];
5215         put(board, pieceType, rank, i, shade);
5216 }
5217
5218 void AddTwoPieces(Board board, int pieceType, int rank)
5219 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5220 {
5221         int i, n=squaresLeft[ANY], j=n-1, k;
5222
5223         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5224         i = seed % k;  // pick one
5225         nrOfShuffles *= k;
5226         seed /= k;
5227         while(i >= j) i -= j--;
5228         j = n - 1 - j; i += j;
5229         put(board, pieceType, rank, j, ANY);
5230         put(board, pieceType, rank, i, ANY);
5231 }
5232
5233 void SetUpShuffle(Board board, int number)
5234 {
5235         int i, p, first=1;
5236
5237         GetPositionNumber(); nrOfShuffles = 1;
5238
5239         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5240         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5241         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5242
5243         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5244
5245         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5246             p = (int) board[0][i];
5247             if(p < (int) BlackPawn) piecesLeft[p] ++;
5248             board[0][i] = EmptySquare;
5249         }
5250
5251         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5252             // shuffles restricted to allow normal castling put KRR first
5253             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5254                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5255             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5256                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5257             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5258                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5259             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5260                 put(board, WhiteRook, 0, 0, ANY);
5261             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5262         }
5263
5264         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5265             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5266             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5267                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5268                 while(piecesLeft[p] >= 2) {
5269                     AddOnePiece(board, p, 0, LITE);
5270                     AddOnePiece(board, p, 0, DARK);
5271                 }
5272                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5273             }
5274
5275         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5276             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5277             // but we leave King and Rooks for last, to possibly obey FRC restriction
5278             if(p == (int)WhiteRook) continue;
5279             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5280             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5281         }
5282
5283         // now everything is placed, except perhaps King (Unicorn) and Rooks
5284
5285         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5286             // Last King gets castling rights
5287             while(piecesLeft[(int)WhiteUnicorn]) {
5288                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5289                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5290             }
5291
5292             while(piecesLeft[(int)WhiteKing]) {
5293                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5294                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5295             }
5296
5297
5298         } else {
5299             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5300             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5301         }
5302
5303         // Only Rooks can be left; simply place them all
5304         while(piecesLeft[(int)WhiteRook]) {
5305                 i = put(board, WhiteRook, 0, 0, ANY);
5306                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5307                         if(first) {
5308                                 first=0;
5309                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5310                         }
5311                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5312                 }
5313         }
5314         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5315             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5316         }
5317
5318         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5319 }
5320
5321 int SetCharTable( char *table, const char * map )
5322 /* [HGM] moved here from winboard.c because of its general usefulness */
5323 /*       Basically a safe strcpy that uses the last character as King */
5324 {
5325     int result = FALSE; int NrPieces;
5326
5327     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5328                     && NrPieces >= 12 && !(NrPieces&1)) {
5329         int i; /* [HGM] Accept even length from 12 to 34 */
5330
5331         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5332         for( i=0; i<NrPieces/2-1; i++ ) {
5333             table[i] = map[i];
5334             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5335         }
5336         table[(int) WhiteKing]  = map[NrPieces/2-1];
5337         table[(int) BlackKing]  = map[NrPieces-1];
5338
5339         result = TRUE;
5340     }
5341
5342     return result;
5343 }
5344
5345 void Prelude(Board board)
5346 {       // [HGM] superchess: random selection of exo-pieces
5347         int i, j, k; ChessSquare p;
5348         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5349
5350         GetPositionNumber(); // use FRC position number
5351
5352         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5353             SetCharTable(pieceToChar, appData.pieceToCharTable);
5354             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5355                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5356         }
5357
5358         j = seed%4;                 seed /= 4;
5359         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5360         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5361         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5362         j = seed%3 + (seed%3 >= j); seed /= 3;
5363         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5364         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5365         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5366         j = seed%3;                 seed /= 3;
5367         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5368         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5369         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5370         j = seed%2 + (seed%2 >= j); seed /= 2;
5371         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5372         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5373         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5374         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5375         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5376         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5377         put(board, exoPieces[0],    0, 0, ANY);
5378         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5379 }
5380
5381 void
5382 InitPosition(redraw)
5383      int redraw;
5384 {
5385     ChessSquare (* pieces)[BOARD_FILES];
5386     int i, j, pawnRow, overrule,
5387     oldx = gameInfo.boardWidth,
5388     oldy = gameInfo.boardHeight,
5389     oldh = gameInfo.holdingsWidth;
5390     static int oldv;
5391
5392     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5393
5394     /* [AS] Initialize pv info list [HGM] and game status */
5395     {
5396         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5397             pvInfoList[i].depth = 0;
5398             boards[i][EP_STATUS] = EP_NONE;
5399             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5400         }
5401
5402         initialRulePlies = 0; /* 50-move counter start */
5403
5404         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5405         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5406     }
5407
5408
5409     /* [HGM] logic here is completely changed. In stead of full positions */
5410     /* the initialized data only consist of the two backranks. The switch */
5411     /* selects which one we will use, which is than copied to the Board   */
5412     /* initialPosition, which for the rest is initialized by Pawns and    */
5413     /* empty squares. This initial position is then copied to boards[0],  */
5414     /* possibly after shuffling, so that it remains available.            */
5415
5416     gameInfo.holdingsWidth = 0; /* default board sizes */
5417     gameInfo.boardWidth    = 8;
5418     gameInfo.boardHeight   = 8;
5419     gameInfo.holdingsSize  = 0;
5420     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5421     for(i=0; i<BOARD_FILES-2; i++)
5422       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5423     initialPosition[EP_STATUS] = EP_NONE;
5424     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5425     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5426          SetCharTable(pieceNickName, appData.pieceNickNames);
5427     else SetCharTable(pieceNickName, "............");
5428     pieces = FIDEArray;
5429
5430     switch (gameInfo.variant) {
5431     case VariantFischeRandom:
5432       shuffleOpenings = TRUE;
5433     default:
5434       break;
5435     case VariantShatranj:
5436       pieces = ShatranjArray;
5437       nrCastlingRights = 0;
5438       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5439       break;
5440     case VariantMakruk:
5441       pieces = makrukArray;
5442       nrCastlingRights = 0;
5443       startedFromSetupPosition = TRUE;
5444       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5445       break;
5446     case VariantTwoKings:
5447       pieces = twoKingsArray;
5448       break;
5449     case VariantCapaRandom:
5450       shuffleOpenings = TRUE;
5451     case VariantCapablanca:
5452       pieces = CapablancaArray;
5453       gameInfo.boardWidth = 10;
5454       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5455       break;
5456     case VariantGothic:
5457       pieces = GothicArray;
5458       gameInfo.boardWidth = 10;
5459       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5460       break;
5461     case VariantSChess:
5462       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5463       gameInfo.holdingsSize = 7;
5464       break;
5465     case VariantJanus:
5466       pieces = JanusArray;
5467       gameInfo.boardWidth = 10;
5468       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5469       nrCastlingRights = 6;
5470         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5471         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5472         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5473         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5474         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5475         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5476       break;
5477     case VariantFalcon:
5478       pieces = FalconArray;
5479       gameInfo.boardWidth = 10;
5480       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5481       break;
5482     case VariantXiangqi:
5483       pieces = XiangqiArray;
5484       gameInfo.boardWidth  = 9;
5485       gameInfo.boardHeight = 10;
5486       nrCastlingRights = 0;
5487       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5488       break;
5489     case VariantShogi:
5490       pieces = ShogiArray;
5491       gameInfo.boardWidth  = 9;
5492       gameInfo.boardHeight = 9;
5493       gameInfo.holdingsSize = 7;
5494       nrCastlingRights = 0;
5495       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5496       break;
5497     case VariantCourier:
5498       pieces = CourierArray;
5499       gameInfo.boardWidth  = 12;
5500       nrCastlingRights = 0;
5501       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5502       break;
5503     case VariantKnightmate:
5504       pieces = KnightmateArray;
5505       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5506       break;
5507     case VariantSpartan:
5508       pieces = SpartanArray;
5509       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5510       break;
5511     case VariantFairy:
5512       pieces = fairyArray;
5513       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5514       break;
5515     case VariantGreat:
5516       pieces = GreatArray;
5517       gameInfo.boardWidth = 10;
5518       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5519       gameInfo.holdingsSize = 8;
5520       break;
5521     case VariantSuper:
5522       pieces = FIDEArray;
5523       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5524       gameInfo.holdingsSize = 8;
5525       startedFromSetupPosition = TRUE;
5526       break;
5527     case VariantCrazyhouse:
5528     case VariantBughouse:
5529       pieces = FIDEArray;
5530       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5531       gameInfo.holdingsSize = 5;
5532       break;
5533     case VariantWildCastle:
5534       pieces = FIDEArray;
5535       /* !!?shuffle with kings guaranteed to be on d or e file */
5536       shuffleOpenings = 1;
5537       break;
5538     case VariantNoCastle:
5539       pieces = FIDEArray;
5540       nrCastlingRights = 0;
5541       /* !!?unconstrained back-rank shuffle */
5542       shuffleOpenings = 1;
5543       break;
5544     }
5545
5546     overrule = 0;
5547     if(appData.NrFiles >= 0) {
5548         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5549         gameInfo.boardWidth = appData.NrFiles;
5550     }
5551     if(appData.NrRanks >= 0) {
5552         gameInfo.boardHeight = appData.NrRanks;
5553     }
5554     if(appData.holdingsSize >= 0) {
5555         i = appData.holdingsSize;
5556         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5557         gameInfo.holdingsSize = i;
5558     }
5559     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5560     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5561         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5562
5563     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5564     if(pawnRow < 1) pawnRow = 1;
5565     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5566
5567     /* User pieceToChar list overrules defaults */
5568     if(appData.pieceToCharTable != NULL)
5569         SetCharTable(pieceToChar, appData.pieceToCharTable);
5570
5571     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5572
5573         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5574             s = (ChessSquare) 0; /* account holding counts in guard band */
5575         for( i=0; i<BOARD_HEIGHT; i++ )
5576             initialPosition[i][j] = s;
5577
5578         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5579         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5580         initialPosition[pawnRow][j] = WhitePawn;
5581         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5582         if(gameInfo.variant == VariantXiangqi) {
5583             if(j&1) {
5584                 initialPosition[pawnRow][j] =
5585                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5586                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5587                    initialPosition[2][j] = WhiteCannon;
5588                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5589                 }
5590             }
5591         }
5592         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5593     }
5594     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5595
5596             j=BOARD_LEFT+1;
5597             initialPosition[1][j] = WhiteBishop;
5598             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5599             j=BOARD_RGHT-2;
5600             initialPosition[1][j] = WhiteRook;
5601             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5602     }
5603
5604     if( nrCastlingRights == -1) {
5605         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5606         /*       This sets default castling rights from none to normal corners   */
5607         /* Variants with other castling rights must set them themselves above    */
5608         nrCastlingRights = 6;
5609
5610         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5611         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5612         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5613         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5614         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5615         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5616      }
5617
5618      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5619      if(gameInfo.variant == VariantGreat) { // promotion commoners
5620         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5621         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5622         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5623         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5624      }
5625      if( gameInfo.variant == VariantSChess ) {
5626       initialPosition[1][0] = BlackMarshall;
5627       initialPosition[2][0] = BlackAngel;
5628       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5629       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5630       initialPosition[1][1] = initialPosition[2][1] = 
5631       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5632      }
5633   if (appData.debugMode) {
5634     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5635   }
5636     if(shuffleOpenings) {
5637         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5638         startedFromSetupPosition = TRUE;
5639     }
5640     if(startedFromPositionFile) {
5641       /* [HGM] loadPos: use PositionFile for every new game */
5642       CopyBoard(initialPosition, filePosition);
5643       for(i=0; i<nrCastlingRights; i++)
5644           initialRights[i] = filePosition[CASTLING][i];
5645       startedFromSetupPosition = TRUE;
5646     }
5647
5648     CopyBoard(boards[0], initialPosition);
5649
5650     if(oldx != gameInfo.boardWidth ||
5651        oldy != gameInfo.boardHeight ||
5652        oldv != gameInfo.variant ||
5653        oldh != gameInfo.holdingsWidth
5654                                          )
5655             InitDrawingSizes(-2 ,0);
5656
5657     oldv = gameInfo.variant;
5658     if (redraw)
5659       DrawPosition(TRUE, boards[currentMove]);
5660 }
5661
5662 void
5663 SendBoard(cps, moveNum)
5664      ChessProgramState *cps;
5665      int moveNum;
5666 {
5667     char message[MSG_SIZ];
5668
5669     if (cps->useSetboard) {
5670       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5671       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5672       SendToProgram(message, cps);
5673       free(fen);
5674
5675     } else {
5676       ChessSquare *bp;
5677       int i, j;
5678       /* Kludge to set black to move, avoiding the troublesome and now
5679        * deprecated "black" command.
5680        */
5681       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5682         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5683
5684       SendToProgram("edit\n", cps);
5685       SendToProgram("#\n", cps);
5686       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5687         bp = &boards[moveNum][i][BOARD_LEFT];
5688         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5689           if ((int) *bp < (int) BlackPawn) {
5690             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5691                     AAA + j, ONE + i);
5692             if(message[0] == '+' || message[0] == '~') {
5693               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5694                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5695                         AAA + j, ONE + i);
5696             }
5697             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5698                 message[1] = BOARD_RGHT   - 1 - j + '1';
5699                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5700             }
5701             SendToProgram(message, cps);
5702           }
5703         }
5704       }
5705
5706       SendToProgram("c\n", cps);
5707       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5708         bp = &boards[moveNum][i][BOARD_LEFT];
5709         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5710           if (((int) *bp != (int) EmptySquare)
5711               && ((int) *bp >= (int) BlackPawn)) {
5712             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5713                     AAA + j, ONE + i);
5714             if(message[0] == '+' || message[0] == '~') {
5715               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5716                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5717                         AAA + j, ONE + i);
5718             }
5719             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5720                 message[1] = BOARD_RGHT   - 1 - j + '1';
5721                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5722             }
5723             SendToProgram(message, cps);
5724           }
5725         }
5726       }
5727
5728       SendToProgram(".\n", cps);
5729     }
5730     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5731 }
5732
5733 ChessSquare
5734 DefaultPromoChoice(int white)
5735 {
5736     ChessSquare result;
5737     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5738         result = WhiteFerz; // no choice
5739     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5740         result= WhiteKing; // in Suicide Q is the last thing we want
5741     else if(gameInfo.variant == VariantSpartan)
5742         result = white ? WhiteQueen : WhiteAngel;
5743     else result = WhiteQueen;
5744     if(!white) result = WHITE_TO_BLACK result;
5745     return result;
5746 }
5747
5748 static int autoQueen; // [HGM] oneclick
5749
5750 int
5751 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5752 {
5753     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5754     /* [HGM] add Shogi promotions */
5755     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5756     ChessSquare piece;
5757     ChessMove moveType;
5758     Boolean premove;
5759
5760     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5761     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5762
5763     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5764       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5765         return FALSE;
5766
5767     piece = boards[currentMove][fromY][fromX];
5768     if(gameInfo.variant == VariantShogi) {
5769         promotionZoneSize = BOARD_HEIGHT/3;
5770         highestPromotingPiece = (int)WhiteFerz;
5771     } else if(gameInfo.variant == VariantMakruk) {
5772         promotionZoneSize = 3;
5773     }
5774
5775     // Treat Lance as Pawn when it is not representing Amazon
5776     if(gameInfo.variant != VariantSuper) {
5777         if(piece == WhiteLance) piece = WhitePawn; else
5778         if(piece == BlackLance) piece = BlackPawn;
5779     }
5780
5781     // next weed out all moves that do not touch the promotion zone at all
5782     if((int)piece >= BlackPawn) {
5783         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5784              return FALSE;
5785         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5786     } else {
5787         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5788            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5789     }
5790
5791     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5792
5793     // weed out mandatory Shogi promotions
5794     if(gameInfo.variant == VariantShogi) {
5795         if(piece >= BlackPawn) {
5796             if(toY == 0 && piece == BlackPawn ||
5797                toY == 0 && piece == BlackQueen ||
5798                toY <= 1 && piece == BlackKnight) {
5799                 *promoChoice = '+';
5800                 return FALSE;
5801             }
5802         } else {
5803             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5804                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5805                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5806                 *promoChoice = '+';
5807                 return FALSE;
5808             }
5809         }
5810     }
5811
5812     // weed out obviously illegal Pawn moves
5813     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5814         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5815         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5816         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5817         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5818         // note we are not allowed to test for valid (non-)capture, due to premove
5819     }
5820
5821     // we either have a choice what to promote to, or (in Shogi) whether to promote
5822     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5823         *promoChoice = PieceToChar(BlackFerz);  // no choice
5824         return FALSE;
5825     }
5826     // no sense asking what we must promote to if it is going to explode...
5827     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5828         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5829         return FALSE;
5830     }
5831     // give caller the default choice even if we will not make it
5832     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5833     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5834     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5835                            && gameInfo.variant != VariantShogi
5836                            && gameInfo.variant != VariantSuper) return FALSE;
5837     if(autoQueen) return FALSE; // predetermined
5838
5839     // suppress promotion popup on illegal moves that are not premoves
5840     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5841               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5842     if(appData.testLegality && !premove) {
5843         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5844                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5845         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5846             return FALSE;
5847     }
5848
5849     return TRUE;
5850 }
5851
5852 int
5853 InPalace(row, column)
5854      int row, column;
5855 {   /* [HGM] for Xiangqi */
5856     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5857          column < (BOARD_WIDTH + 4)/2 &&
5858          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5859     return FALSE;
5860 }
5861
5862 int
5863 PieceForSquare (x, y)
5864      int x;
5865      int y;
5866 {
5867   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5868      return -1;
5869   else
5870      return boards[currentMove][y][x];
5871 }
5872
5873 int
5874 OKToStartUserMove(x, y)
5875      int x, y;
5876 {
5877     ChessSquare from_piece;
5878     int white_piece;
5879
5880     if (matchMode) return FALSE;
5881     if (gameMode == EditPosition) return TRUE;
5882
5883     if (x >= 0 && y >= 0)
5884       from_piece = boards[currentMove][y][x];
5885     else
5886       from_piece = EmptySquare;
5887
5888     if (from_piece == EmptySquare) return FALSE;
5889
5890     white_piece = (int)from_piece >= (int)WhitePawn &&
5891       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5892
5893     switch (gameMode) {
5894       case PlayFromGameFile:
5895       case AnalyzeFile:
5896       case TwoMachinesPlay:
5897       case EndOfGame:
5898         return FALSE;
5899
5900       case IcsObserving:
5901       case IcsIdle:
5902         return FALSE;
5903
5904       case MachinePlaysWhite:
5905       case IcsPlayingBlack:
5906         if (appData.zippyPlay) return FALSE;
5907         if (white_piece) {
5908             DisplayMoveError(_("You are playing Black"));
5909             return FALSE;
5910         }
5911         break;
5912
5913       case MachinePlaysBlack:
5914       case IcsPlayingWhite:
5915         if (appData.zippyPlay) return FALSE;
5916         if (!white_piece) {
5917             DisplayMoveError(_("You are playing White"));
5918             return FALSE;
5919         }
5920         break;
5921
5922       case EditGame:
5923         if (!white_piece && WhiteOnMove(currentMove)) {
5924             DisplayMoveError(_("It is White's turn"));
5925             return FALSE;
5926         }
5927         if (white_piece && !WhiteOnMove(currentMove)) {
5928             DisplayMoveError(_("It is Black's turn"));
5929             return FALSE;
5930         }
5931         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5932             /* Editing correspondence game history */
5933             /* Could disallow this or prompt for confirmation */
5934             cmailOldMove = -1;
5935         }
5936         break;
5937
5938       case BeginningOfGame:
5939         if (appData.icsActive) return FALSE;
5940         if (!appData.noChessProgram) {
5941             if (!white_piece) {
5942                 DisplayMoveError(_("You are playing White"));
5943                 return FALSE;
5944             }
5945         }
5946         break;
5947
5948       case Training:
5949         if (!white_piece && WhiteOnMove(currentMove)) {
5950             DisplayMoveError(_("It is White's turn"));
5951             return FALSE;
5952         }
5953         if (white_piece && !WhiteOnMove(currentMove)) {
5954             DisplayMoveError(_("It is Black's turn"));
5955             return FALSE;
5956         }
5957         break;
5958
5959       default:
5960       case IcsExamining:
5961         break;
5962     }
5963     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5964         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5965         && gameMode != AnalyzeFile && gameMode != Training) {
5966         DisplayMoveError(_("Displayed position is not current"));
5967         return FALSE;
5968     }
5969     return TRUE;
5970 }
5971
5972 Boolean
5973 OnlyMove(int *x, int *y, Boolean captures) {
5974     DisambiguateClosure cl;
5975     if (appData.zippyPlay) return FALSE;
5976     switch(gameMode) {
5977       case MachinePlaysBlack:
5978       case IcsPlayingWhite:
5979       case BeginningOfGame:
5980         if(!WhiteOnMove(currentMove)) return FALSE;
5981         break;
5982       case MachinePlaysWhite:
5983       case IcsPlayingBlack:
5984         if(WhiteOnMove(currentMove)) return FALSE;
5985         break;
5986       case EditGame:
5987         break;
5988       default:
5989         return FALSE;
5990     }
5991     cl.pieceIn = EmptySquare;
5992     cl.rfIn = *y;
5993     cl.ffIn = *x;
5994     cl.rtIn = -1;
5995     cl.ftIn = -1;
5996     cl.promoCharIn = NULLCHAR;
5997     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5998     if( cl.kind == NormalMove ||
5999         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6000         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6001         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6002       fromX = cl.ff;
6003       fromY = cl.rf;
6004       *x = cl.ft;
6005       *y = cl.rt;
6006       return TRUE;
6007     }
6008     if(cl.kind != ImpossibleMove) return FALSE;
6009     cl.pieceIn = EmptySquare;
6010     cl.rfIn = -1;
6011     cl.ffIn = -1;
6012     cl.rtIn = *y;
6013     cl.ftIn = *x;
6014     cl.promoCharIn = NULLCHAR;
6015     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6016     if( cl.kind == NormalMove ||
6017         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6018         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6019         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6020       fromX = cl.ff;
6021       fromY = cl.rf;
6022       *x = cl.ft;
6023       *y = cl.rt;
6024       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6025       return TRUE;
6026     }
6027     return FALSE;
6028 }
6029
6030 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6031 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6032 int lastLoadGameUseList = FALSE;
6033 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6034 ChessMove lastLoadGameStart = EndOfFile;
6035
6036 void
6037 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6038      int fromX, fromY, toX, toY;
6039      int promoChar;
6040 {
6041     ChessMove moveType;
6042     ChessSquare pdown, pup;
6043
6044     /* Check if the user is playing in turn.  This is complicated because we
6045        let the user "pick up" a piece before it is his turn.  So the piece he
6046        tried to pick up may have been captured by the time he puts it down!
6047        Therefore we use the color the user is supposed to be playing in this
6048        test, not the color of the piece that is currently on the starting
6049        square---except in EditGame mode, where the user is playing both
6050        sides; fortunately there the capture race can't happen.  (It can
6051        now happen in IcsExamining mode, but that's just too bad.  The user
6052        will get a somewhat confusing message in that case.)
6053        */
6054
6055     switch (gameMode) {
6056       case PlayFromGameFile:
6057       case AnalyzeFile:
6058       case TwoMachinesPlay:
6059       case EndOfGame:
6060       case IcsObserving:
6061       case IcsIdle:
6062         /* We switched into a game mode where moves are not accepted,
6063            perhaps while the mouse button was down. */
6064         return;
6065
6066       case MachinePlaysWhite:
6067         /* User is moving for Black */
6068         if (WhiteOnMove(currentMove)) {
6069             DisplayMoveError(_("It is White's turn"));
6070             return;
6071         }
6072         break;
6073
6074       case MachinePlaysBlack:
6075         /* User is moving for White */
6076         if (!WhiteOnMove(currentMove)) {
6077             DisplayMoveError(_("It is Black's turn"));
6078             return;
6079         }
6080         break;
6081
6082       case EditGame:
6083       case IcsExamining:
6084       case BeginningOfGame:
6085       case AnalyzeMode:
6086       case Training:
6087         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6088         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6089             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6090             /* User is moving for Black */
6091             if (WhiteOnMove(currentMove)) {
6092                 DisplayMoveError(_("It is White's turn"));
6093                 return;
6094             }
6095         } else {
6096             /* User is moving for White */
6097             if (!WhiteOnMove(currentMove)) {
6098                 DisplayMoveError(_("It is Black's turn"));
6099                 return;
6100             }
6101         }
6102         break;
6103
6104       case IcsPlayingBlack:
6105         /* User is moving for Black */
6106         if (WhiteOnMove(currentMove)) {
6107             if (!appData.premove) {
6108                 DisplayMoveError(_("It is White's turn"));
6109             } else if (toX >= 0 && toY >= 0) {
6110                 premoveToX = toX;
6111                 premoveToY = toY;
6112                 premoveFromX = fromX;
6113                 premoveFromY = fromY;
6114                 premovePromoChar = promoChar;
6115                 gotPremove = 1;
6116                 if (appData.debugMode)
6117                     fprintf(debugFP, "Got premove: fromX %d,"
6118                             "fromY %d, toX %d, toY %d\n",
6119                             fromX, fromY, toX, toY);
6120             }
6121             return;
6122         }
6123         break;
6124
6125       case IcsPlayingWhite:
6126         /* User is moving for White */
6127         if (!WhiteOnMove(currentMove)) {
6128             if (!appData.premove) {
6129                 DisplayMoveError(_("It is Black's turn"));
6130             } else if (toX >= 0 && toY >= 0) {
6131                 premoveToX = toX;
6132                 premoveToY = toY;
6133                 premoveFromX = fromX;
6134                 premoveFromY = fromY;
6135                 premovePromoChar = promoChar;
6136                 gotPremove = 1;
6137                 if (appData.debugMode)
6138                     fprintf(debugFP, "Got premove: fromX %d,"
6139                             "fromY %d, toX %d, toY %d\n",
6140                             fromX, fromY, toX, toY);
6141             }
6142             return;
6143         }
6144         break;
6145
6146       default:
6147         break;
6148
6149       case EditPosition:
6150         /* EditPosition, empty square, or different color piece;
6151            click-click move is possible */
6152         if (toX == -2 || toY == -2) {
6153             boards[0][fromY][fromX] = EmptySquare;
6154             DrawPosition(FALSE, boards[currentMove]);
6155             return;
6156         } else if (toX >= 0 && toY >= 0) {
6157             boards[0][toY][toX] = boards[0][fromY][fromX];
6158             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6159                 if(boards[0][fromY][0] != EmptySquare) {
6160                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6161                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6162                 }
6163             } else
6164             if(fromX == BOARD_RGHT+1) {
6165                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6166                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6167                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6168                 }
6169             } else
6170             boards[0][fromY][fromX] = EmptySquare;
6171             DrawPosition(FALSE, boards[currentMove]);
6172             return;
6173         }
6174         return;
6175     }
6176
6177     if(toX < 0 || toY < 0) return;
6178     pdown = boards[currentMove][fromY][fromX];
6179     pup = boards[currentMove][toY][toX];
6180
6181     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6182     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6183          if( pup != EmptySquare ) return;
6184          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6185            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6186                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6187            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6188            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6189            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6190            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6191          fromY = DROP_RANK;
6192     }
6193
6194     /* [HGM] always test for legality, to get promotion info */
6195     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6196                                          fromY, fromX, toY, toX, promoChar);
6197     /* [HGM] but possibly ignore an IllegalMove result */
6198     if (appData.testLegality) {
6199         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6200             DisplayMoveError(_("Illegal move"));
6201             return;
6202         }
6203     }
6204
6205     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6206 }
6207
6208 /* Common tail of UserMoveEvent and DropMenuEvent */
6209 int
6210 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6211      ChessMove moveType;
6212      int fromX, fromY, toX, toY;
6213      /*char*/int promoChar;
6214 {
6215     char *bookHit = 0;
6216
6217     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6218         // [HGM] superchess: suppress promotions to non-available piece
6219         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6220         if(WhiteOnMove(currentMove)) {
6221             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6222         } else {
6223             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6224         }
6225     }
6226
6227     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6228        move type in caller when we know the move is a legal promotion */
6229     if(moveType == NormalMove && promoChar)
6230         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6231
6232     /* [HGM] <popupFix> The following if has been moved here from
6233        UserMoveEvent(). Because it seemed to belong here (why not allow
6234        piece drops in training games?), and because it can only be
6235        performed after it is known to what we promote. */
6236     if (gameMode == Training) {
6237       /* compare the move played on the board to the next move in the
6238        * game. If they match, display the move and the opponent's response.
6239        * If they don't match, display an error message.
6240        */
6241       int saveAnimate;
6242       Board testBoard;
6243       CopyBoard(testBoard, boards[currentMove]);
6244       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6245
6246       if (CompareBoards(testBoard, boards[currentMove+1])) {
6247         ForwardInner(currentMove+1);
6248
6249         /* Autoplay the opponent's response.
6250          * if appData.animate was TRUE when Training mode was entered,
6251          * the response will be animated.
6252          */
6253         saveAnimate = appData.animate;
6254         appData.animate = animateTraining;
6255         ForwardInner(currentMove+1);
6256         appData.animate = saveAnimate;
6257
6258         /* check for the end of the game */
6259         if (currentMove >= forwardMostMove) {
6260           gameMode = PlayFromGameFile;
6261           ModeHighlight();
6262           SetTrainingModeOff();
6263           DisplayInformation(_("End of game"));
6264         }
6265       } else {
6266         DisplayError(_("Incorrect move"), 0);
6267       }
6268       return 1;
6269     }
6270
6271   /* Ok, now we know that the move is good, so we can kill
6272      the previous line in Analysis Mode */
6273   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6274                                 && currentMove < forwardMostMove) {
6275     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6276     else forwardMostMove = currentMove;
6277   }
6278
6279   /* If we need the chess program but it's dead, restart it */
6280   ResurrectChessProgram();
6281
6282   /* A user move restarts a paused game*/
6283   if (pausing)
6284     PauseEvent();
6285
6286   thinkOutput[0] = NULLCHAR;
6287
6288   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6289
6290   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6291     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6292     return 1;
6293   }
6294
6295   if (gameMode == BeginningOfGame) {
6296     if (appData.noChessProgram) {
6297       gameMode = EditGame;
6298       SetGameInfo();
6299     } else {
6300       char buf[MSG_SIZ];
6301       gameMode = MachinePlaysBlack;
6302       StartClocks();
6303       SetGameInfo();
6304       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6305       DisplayTitle(buf);
6306       if (first.sendName) {
6307         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6308         SendToProgram(buf, &first);
6309       }
6310       StartClocks();
6311     }
6312     ModeHighlight();
6313   }
6314
6315   /* Relay move to ICS or chess engine */
6316   if (appData.icsActive) {
6317     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6318         gameMode == IcsExamining) {
6319       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6320         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6321         SendToICS("draw ");
6322         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6323       }
6324       // also send plain move, in case ICS does not understand atomic claims
6325       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6326       ics_user_moved = 1;
6327     }
6328   } else {
6329     if (first.sendTime && (gameMode == BeginningOfGame ||
6330                            gameMode == MachinePlaysWhite ||
6331                            gameMode == MachinePlaysBlack)) {
6332       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6333     }
6334     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6335          // [HGM] book: if program might be playing, let it use book
6336         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6337         first.maybeThinking = TRUE;
6338     } else SendMoveToProgram(forwardMostMove-1, &first);
6339     if (currentMove == cmailOldMove + 1) {
6340       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6341     }
6342   }
6343
6344   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6345
6346   switch (gameMode) {
6347   case EditGame:
6348     if(appData.testLegality)
6349     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6350     case MT_NONE:
6351     case MT_CHECK:
6352       break;
6353     case MT_CHECKMATE:
6354     case MT_STAINMATE:
6355       if (WhiteOnMove(currentMove)) {
6356         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6357       } else {
6358         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6359       }
6360       break;
6361     case MT_STALEMATE:
6362       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6363       break;
6364     }
6365     break;
6366
6367   case MachinePlaysBlack:
6368   case MachinePlaysWhite:
6369     /* disable certain menu options while machine is thinking */
6370     SetMachineThinkingEnables();
6371     break;
6372
6373   default:
6374     break;
6375   }
6376
6377   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6378   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6379
6380   if(bookHit) { // [HGM] book: simulate book reply
6381         static char bookMove[MSG_SIZ]; // a bit generous?
6382
6383         programStats.nodes = programStats.depth = programStats.time =
6384         programStats.score = programStats.got_only_move = 0;
6385         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6386
6387         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6388         strcat(bookMove, bookHit);
6389         HandleMachineMove(bookMove, &first);
6390   }
6391   return 1;
6392 }
6393
6394 void
6395 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6396      Board board;
6397      int flags;
6398      ChessMove kind;
6399      int rf, ff, rt, ft;
6400      VOIDSTAR closure;
6401 {
6402     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6403     Markers *m = (Markers *) closure;
6404     if(rf == fromY && ff == fromX)
6405         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6406                          || kind == WhiteCapturesEnPassant
6407                          || kind == BlackCapturesEnPassant);
6408     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6409 }
6410
6411 void
6412 MarkTargetSquares(int clear)
6413 {
6414   int x, y;
6415   if(!appData.markers || !appData.highlightDragging ||
6416      !appData.testLegality || gameMode == EditPosition) return;
6417   if(clear) {
6418     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6419   } else {
6420     int capt = 0;
6421     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6422     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6423       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6424       if(capt)
6425       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6426     }
6427   }
6428   DrawPosition(TRUE, NULL);
6429 }
6430
6431 int
6432 Explode(Board board, int fromX, int fromY, int toX, int toY)
6433 {
6434     if(gameInfo.variant == VariantAtomic &&
6435        (board[toY][toX] != EmptySquare ||                     // capture?
6436         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6437                          board[fromY][fromX] == BlackPawn   )
6438       )) {
6439         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6440         return TRUE;
6441     }
6442     return FALSE;
6443 }
6444
6445 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6446
6447 int CanPromote(ChessSquare piece, int y)
6448 {
6449         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6450         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6451         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6452            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6453            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6454                                                   gameInfo.variant == VariantMakruk) return FALSE;
6455         return (piece == BlackPawn && y == 1 ||
6456                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6457                 piece == BlackLance && y == 1 ||
6458                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6459 }
6460
6461 void LeftClick(ClickType clickType, int xPix, int yPix)
6462 {
6463     int x, y;
6464     Boolean saveAnimate;
6465     static int second = 0, promotionChoice = 0, clearFlag = 0;
6466     char promoChoice = NULLCHAR;
6467     ChessSquare piece;
6468
6469     if(appData.seekGraph && appData.icsActive && loggedOn &&
6470         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6471         SeekGraphClick(clickType, xPix, yPix, 0);
6472         return;
6473     }
6474
6475     if (clickType == Press) ErrorPopDown();
6476     MarkTargetSquares(1);
6477
6478     x = EventToSquare(xPix, BOARD_WIDTH);
6479     y = EventToSquare(yPix, BOARD_HEIGHT);
6480     if (!flipView && y >= 0) {
6481         y = BOARD_HEIGHT - 1 - y;
6482     }
6483     if (flipView && x >= 0) {
6484         x = BOARD_WIDTH - 1 - x;
6485     }
6486
6487     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6488         defaultPromoChoice = promoSweep;
6489         promoSweep = EmptySquare;   // terminate sweep
6490         promoDefaultAltered = TRUE;
6491         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6492     }
6493
6494     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6495         if(clickType == Release) return; // ignore upclick of click-click destination
6496         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6497         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6498         if(gameInfo.holdingsWidth &&
6499                 (WhiteOnMove(currentMove)
6500                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6501                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6502             // click in right holdings, for determining promotion piece
6503             ChessSquare p = boards[currentMove][y][x];
6504             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6505             if(p != EmptySquare) {
6506                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6507                 fromX = fromY = -1;
6508                 return;
6509             }
6510         }
6511         DrawPosition(FALSE, boards[currentMove]);
6512         return;
6513     }
6514
6515     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6516     if(clickType == Press
6517             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6518               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6519               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6520         return;
6521
6522     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6523         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6524
6525     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6526         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6527                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6528         defaultPromoChoice = DefaultPromoChoice(side);
6529     }
6530
6531     autoQueen = appData.alwaysPromoteToQueen;
6532
6533     if (fromX == -1) {
6534       int originalY = y;
6535       gatingPiece = EmptySquare;
6536       if (clickType != Press) {
6537         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6538             DragPieceEnd(xPix, yPix); dragging = 0;
6539             DrawPosition(FALSE, NULL);
6540         }
6541         return;
6542       }
6543       fromX = x; fromY = y;
6544       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6545          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6546          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6547             /* First square */
6548             if (OKToStartUserMove(fromX, fromY)) {
6549                 second = 0;
6550                 MarkTargetSquares(0);
6551                 DragPieceBegin(xPix, yPix); dragging = 1;
6552                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6553                     promoSweep = defaultPromoChoice;
6554                     selectFlag = 0; lastX = xPix; lastY = yPix;
6555                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6556                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6557                 }
6558                 if (appData.highlightDragging) {
6559                     SetHighlights(fromX, fromY, -1, -1);
6560                 }
6561             } else fromX = fromY = -1;
6562             return;
6563         }
6564     }
6565
6566     /* fromX != -1 */
6567     if (clickType == Press && gameMode != EditPosition) {
6568         ChessSquare fromP;
6569         ChessSquare toP;
6570         int frc;
6571
6572         // ignore off-board to clicks
6573         if(y < 0 || x < 0) return;
6574
6575         /* Check if clicking again on the same color piece */
6576         fromP = boards[currentMove][fromY][fromX];
6577         toP = boards[currentMove][y][x];
6578         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6579         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6580              WhitePawn <= toP && toP <= WhiteKing &&
6581              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6582              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6583             (BlackPawn <= fromP && fromP <= BlackKing &&
6584              BlackPawn <= toP && toP <= BlackKing &&
6585              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6586              !(fromP == BlackKing && toP == BlackRook && frc))) {
6587             /* Clicked again on same color piece -- changed his mind */
6588             second = (x == fromX && y == fromY);
6589             promoDefaultAltered = FALSE;
6590            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6591             if (appData.highlightDragging) {
6592                 SetHighlights(x, y, -1, -1);
6593             } else {
6594                 ClearHighlights();
6595             }
6596             if (OKToStartUserMove(x, y)) {
6597                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6598                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6599                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6600                  gatingPiece = boards[currentMove][fromY][fromX];
6601                 else gatingPiece = EmptySquare;
6602                 fromX = x;
6603                 fromY = y; dragging = 1;
6604                 MarkTargetSquares(0);
6605                 DragPieceBegin(xPix, yPix);
6606                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6607                     promoSweep = defaultPromoChoice;
6608                     selectFlag = 0; lastX = xPix; lastY = yPix;
6609                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6610                 }
6611             }
6612            }
6613            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6614            second = FALSE; 
6615         }
6616         // ignore clicks on holdings
6617         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6618     }
6619
6620     if (clickType == Release && x == fromX && y == fromY) {
6621         DragPieceEnd(xPix, yPix); dragging = 0;
6622         if(clearFlag) {
6623             // a deferred attempt to click-click move an empty square on top of a piece
6624             boards[currentMove][y][x] = EmptySquare;
6625             ClearHighlights();
6626             DrawPosition(FALSE, boards[currentMove]);
6627             fromX = fromY = -1; clearFlag = 0;
6628             return;
6629         }
6630         if (appData.animateDragging) {
6631             /* Undo animation damage if any */
6632             DrawPosition(FALSE, NULL);
6633         }
6634         if (second) {
6635             /* Second up/down in same square; just abort move */
6636             second = 0;
6637             fromX = fromY = -1;
6638             gatingPiece = EmptySquare;
6639             ClearHighlights();
6640             gotPremove = 0;
6641             ClearPremoveHighlights();
6642         } else {
6643             /* First upclick in same square; start click-click mode */
6644             SetHighlights(x, y, -1, -1);
6645         }
6646         return;
6647     }
6648
6649     clearFlag = 0;
6650
6651     /* we now have a different from- and (possibly off-board) to-square */
6652     /* Completed move */
6653     toX = x;
6654     toY = y;
6655     saveAnimate = appData.animate;
6656     if (clickType == Press) {
6657         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6658             // must be Edit Position mode with empty-square selected
6659             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6660             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6661             return;
6662         }
6663         /* Finish clickclick move */
6664         if (appData.animate || appData.highlightLastMove) {
6665             SetHighlights(fromX, fromY, toX, toY);
6666         } else {
6667             ClearHighlights();
6668         }
6669     } else {
6670         /* Finish drag move */
6671         if (appData.highlightLastMove) {
6672             SetHighlights(fromX, fromY, toX, toY);
6673         } else {
6674             ClearHighlights();
6675         }
6676         DragPieceEnd(xPix, yPix); dragging = 0;
6677         /* Don't animate move and drag both */
6678         appData.animate = FALSE;
6679     }
6680
6681     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6682     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6683         ChessSquare piece = boards[currentMove][fromY][fromX];
6684         if(gameMode == EditPosition && piece != EmptySquare &&
6685            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6686             int n;
6687
6688             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6689                 n = PieceToNumber(piece - (int)BlackPawn);
6690                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6691                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6692                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6693             } else
6694             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6695                 n = PieceToNumber(piece);
6696                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6697                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6698                 boards[currentMove][n][BOARD_WIDTH-2]++;
6699             }
6700             boards[currentMove][fromY][fromX] = EmptySquare;
6701         }
6702         ClearHighlights();
6703         fromX = fromY = -1;
6704         DrawPosition(TRUE, boards[currentMove]);
6705         return;
6706     }
6707
6708     // off-board moves should not be highlighted
6709     if(x < 0 || y < 0) ClearHighlights();
6710
6711     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6712
6713     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6714         SetHighlights(fromX, fromY, toX, toY);
6715         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6716             // [HGM] super: promotion to captured piece selected from holdings
6717             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6718             promotionChoice = TRUE;
6719             // kludge follows to temporarily execute move on display, without promoting yet
6720             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6721             boards[currentMove][toY][toX] = p;
6722             DrawPosition(FALSE, boards[currentMove]);
6723             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6724             boards[currentMove][toY][toX] = q;
6725             DisplayMessage("Click in holdings to choose piece", "");
6726             return;
6727         }
6728         PromotionPopUp();
6729     } else {
6730         int oldMove = currentMove;
6731         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6732         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6733         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6734         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6735            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6736             DrawPosition(TRUE, boards[currentMove]);
6737         fromX = fromY = -1;
6738     }
6739     appData.animate = saveAnimate;
6740     if (appData.animate || appData.animateDragging) {
6741         /* Undo animation damage if needed */
6742         DrawPosition(FALSE, NULL);
6743     }
6744 }
6745
6746 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6747 {   // front-end-free part taken out of PieceMenuPopup
6748     int whichMenu; int xSqr, ySqr;
6749
6750     if(seekGraphUp) { // [HGM] seekgraph
6751         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6752         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6753         return -2;
6754     }
6755
6756     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6757          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6758         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6759         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6760         if(action == Press)   {
6761             originalFlip = flipView;
6762             flipView = !flipView; // temporarily flip board to see game from partners perspective
6763             DrawPosition(TRUE, partnerBoard);
6764             DisplayMessage(partnerStatus, "");
6765             partnerUp = TRUE;
6766         } else if(action == Release) {
6767             flipView = originalFlip;
6768             DrawPosition(TRUE, boards[currentMove]);
6769             partnerUp = FALSE;
6770         }
6771         return -2;
6772     }
6773
6774     xSqr = EventToSquare(x, BOARD_WIDTH);
6775     ySqr = EventToSquare(y, BOARD_HEIGHT);
6776     if (action == Release) {
6777         if(pieceSweep != EmptySquare) {
6778             EditPositionMenuEvent(pieceSweep, toX, toY);
6779             pieceSweep = EmptySquare;
6780         } else UnLoadPV(); // [HGM] pv
6781     }
6782     if (action != Press) return -2; // return code to be ignored
6783     switch (gameMode) {
6784       case IcsExamining:
6785         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6786       case EditPosition:
6787         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6788         if (xSqr < 0 || ySqr < 0) return -1;
6789         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6790         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6791         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6792         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6793         NextPiece(0);
6794         return -2;\r
6795       case IcsObserving:
6796         if(!appData.icsEngineAnalyze) return -1;
6797       case IcsPlayingWhite:
6798       case IcsPlayingBlack:
6799         if(!appData.zippyPlay) goto noZip;
6800       case AnalyzeMode:
6801       case AnalyzeFile:
6802       case MachinePlaysWhite:
6803       case MachinePlaysBlack:
6804       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6805         if (!appData.dropMenu) {
6806           LoadPV(x, y);
6807           return 2; // flag front-end to grab mouse events
6808         }
6809         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6810            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6811       case EditGame:
6812       noZip:
6813         if (xSqr < 0 || ySqr < 0) return -1;
6814         if (!appData.dropMenu || appData.testLegality &&
6815             gameInfo.variant != VariantBughouse &&
6816             gameInfo.variant != VariantCrazyhouse) return -1;
6817         whichMenu = 1; // drop menu
6818         break;
6819       default:
6820         return -1;
6821     }
6822
6823     if (((*fromX = xSqr) < 0) ||
6824         ((*fromY = ySqr) < 0)) {
6825         *fromX = *fromY = -1;
6826         return -1;
6827     }
6828     if (flipView)
6829       *fromX = BOARD_WIDTH - 1 - *fromX;
6830     else
6831       *fromY = BOARD_HEIGHT - 1 - *fromY;
6832
6833     return whichMenu;
6834 }
6835
6836 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6837 {
6838 //    char * hint = lastHint;
6839     FrontEndProgramStats stats;
6840
6841     stats.which = cps == &first ? 0 : 1;
6842     stats.depth = cpstats->depth;
6843     stats.nodes = cpstats->nodes;
6844     stats.score = cpstats->score;
6845     stats.time = cpstats->time;
6846     stats.pv = cpstats->movelist;
6847     stats.hint = lastHint;
6848     stats.an_move_index = 0;
6849     stats.an_move_count = 0;
6850
6851     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6852         stats.hint = cpstats->move_name;
6853         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6854         stats.an_move_count = cpstats->nr_moves;
6855     }
6856
6857     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
6858
6859     SetProgramStats( &stats );
6860 }
6861
6862 void
6863 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6864 {       // count all piece types
6865         int p, f, r;
6866         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6867         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6868         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6869                 p = board[r][f];
6870                 pCnt[p]++;
6871                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6872                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6873                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6874                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6875                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6876                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6877         }
6878 }
6879
6880 int
6881 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6882 {
6883         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6884         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6885
6886         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6887         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6888         if(myPawns == 2 && nMine == 3) // KPP
6889             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6890         if(myPawns == 1 && nMine == 2) // KP
6891             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6892         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6893             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6894         if(myPawns) return FALSE;
6895         if(pCnt[WhiteRook+side])
6896             return pCnt[BlackRook-side] ||
6897                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6898                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6899                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6900         if(pCnt[WhiteCannon+side]) {
6901             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6902             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6903         }
6904         if(pCnt[WhiteKnight+side])
6905             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6906         return FALSE;
6907 }
6908
6909 int
6910 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6911 {
6912         VariantClass v = gameInfo.variant;
6913
6914         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6915         if(v == VariantShatranj) return TRUE; // always winnable through baring
6916         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6917         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6918
6919         if(v == VariantXiangqi) {
6920                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6921
6922                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6923                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6924                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6925                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6926                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6927                 if(stale) // we have at least one last-rank P plus perhaps C
6928                     return majors // KPKX
6929                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6930                 else // KCA*E*
6931                     return pCnt[WhiteFerz+side] // KCAK
6932                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6933                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6934                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6935
6936         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6937                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6938
6939                 if(nMine == 1) return FALSE; // bare King
6940                 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
6941                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6942                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6943                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6944                 if(pCnt[WhiteKnight+side])
6945                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6946                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6947                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6948                 if(nBishops)
6949                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6950                 if(pCnt[WhiteAlfil+side])
6951                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6952                 if(pCnt[WhiteWazir+side])
6953                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6954         }
6955
6956         return TRUE;
6957 }
6958
6959 int
6960 Adjudicate(ChessProgramState *cps)
6961 {       // [HGM] some adjudications useful with buggy engines
6962         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6963         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6964         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6965         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6966         int k, count = 0; static int bare = 1;
6967         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6968         Boolean canAdjudicate = !appData.icsActive;
6969
6970         // most tests only when we understand the game, i.e. legality-checking on
6971             if( appData.testLegality )
6972             {   /* [HGM] Some more adjudications for obstinate engines */
6973                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6974                 static int moveCount = 6;
6975                 ChessMove result;
6976                 char *reason = NULL;
6977
6978                 /* Count what is on board. */
6979                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6980
6981                 /* Some material-based adjudications that have to be made before stalemate test */
6982                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6983                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6984                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6985                      if(canAdjudicate && appData.checkMates) {
6986                          if(engineOpponent)
6987                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6988                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6989                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6990                          return 1;
6991                      }
6992                 }
6993
6994                 /* Bare King in Shatranj (loses) or Losers (wins) */
6995                 if( nrW == 1 || nrB == 1) {
6996                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6997                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6998                      if(canAdjudicate && appData.checkMates) {
6999                          if(engineOpponent)
7000                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7001                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7002                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7003                          return 1;
7004                      }
7005                   } else
7006                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7007                   {    /* bare King */
7008                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7009                         if(canAdjudicate && appData.checkMates) {
7010                             /* but only adjudicate if adjudication enabled */
7011                             if(engineOpponent)
7012                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7013                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7014                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7015                             return 1;
7016                         }
7017                   }
7018                 } else bare = 1;
7019
7020
7021             // don't wait for engine to announce game end if we can judge ourselves
7022             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7023               case MT_CHECK:
7024                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7025                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7026                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7027                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7028                             checkCnt++;
7029                         if(checkCnt >= 2) {
7030                             reason = "Xboard adjudication: 3rd check";
7031                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7032                             break;
7033                         }
7034                     }
7035                 }
7036               case MT_NONE:
7037               default:
7038                 break;
7039               case MT_STALEMATE:
7040               case MT_STAINMATE:
7041                 reason = "Xboard adjudication: Stalemate";
7042                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7043                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7044                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7045                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7046                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7047                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7048                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7049                                                                         EP_CHECKMATE : EP_WINS);
7050                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7051                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7052                 }
7053                 break;
7054               case MT_CHECKMATE:
7055                 reason = "Xboard adjudication: Checkmate";
7056                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7057                 break;
7058             }
7059
7060                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7061                     case EP_STALEMATE:
7062                         result = GameIsDrawn; break;
7063                     case EP_CHECKMATE:
7064                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7065                     case EP_WINS:
7066                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7067                     default:
7068                         result = EndOfFile;
7069                 }
7070                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7071                     if(engineOpponent)
7072                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7073                     GameEnds( result, reason, GE_XBOARD );
7074                     return 1;
7075                 }
7076
7077                 /* Next absolutely insufficient mating material. */
7078                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7079                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7080                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7081
7082                      /* always flag draws, for judging claims */
7083                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7084
7085                      if(canAdjudicate && appData.materialDraws) {
7086                          /* but only adjudicate them if adjudication enabled */
7087                          if(engineOpponent) {
7088                            SendToProgram("force\n", engineOpponent); // suppress reply
7089                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7090                          }
7091                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7092                          return 1;
7093                      }
7094                 }
7095
7096                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7097                 if(gameInfo.variant == VariantXiangqi ?
7098                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7099                  : nrW + nrB == 4 &&
7100                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7101                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7102                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7103                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7104                    ) ) {
7105                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7106                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7107                           if(engineOpponent) {
7108                             SendToProgram("force\n", engineOpponent); // suppress reply
7109                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7110                           }
7111                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7112                           return 1;
7113                      }
7114                 } else moveCount = 6;
7115             }
7116         if (appData.debugMode) { int i;
7117             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7118                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7119                     appData.drawRepeats);
7120             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7121               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7122
7123         }
7124
7125         // Repetition draws and 50-move rule can be applied independently of legality testing
7126
7127                 /* Check for rep-draws */
7128                 count = 0;
7129                 for(k = forwardMostMove-2;
7130                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7131                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7132                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7133                     k-=2)
7134                 {   int rights=0;
7135                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7136                         /* compare castling rights */
7137                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7138                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7139                                 rights++; /* King lost rights, while rook still had them */
7140                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7141                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7142                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7143                                    rights++; /* but at least one rook lost them */
7144                         }
7145                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7146                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7147                                 rights++;
7148                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7149                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7150                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7151                                    rights++;
7152                         }
7153                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7154                             && appData.drawRepeats > 1) {
7155                              /* adjudicate after user-specified nr of repeats */
7156                              int result = GameIsDrawn;
7157                              char *details = "XBoard adjudication: repetition draw";
7158                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7159                                 // [HGM] xiangqi: check for forbidden perpetuals
7160                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7161                                 for(m=forwardMostMove; m>k; m-=2) {
7162                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7163                                         ourPerpetual = 0; // the current mover did not always check
7164                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7165                                         hisPerpetual = 0; // the opponent did not always check
7166                                 }
7167                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7168                                                                         ourPerpetual, hisPerpetual);
7169                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7170                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7171                                     details = "Xboard adjudication: perpetual checking";
7172                                 } else
7173                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7174                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7175                                 } else
7176                                 // Now check for perpetual chases
7177                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7178                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7179                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7180                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7181                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7182                                         details = "Xboard adjudication: perpetual chasing";
7183                                     } else
7184                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7185                                         break; // Abort repetition-checking loop.
7186                                 }
7187                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7188                              }
7189                              if(engineOpponent) {
7190                                SendToProgram("force\n", engineOpponent); // suppress reply
7191                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7192                              }
7193                              GameEnds( result, details, GE_XBOARD );
7194                              return 1;
7195                         }
7196                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7197                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7198                     }
7199                 }
7200
7201                 /* Now we test for 50-move draws. Determine ply count */
7202                 count = forwardMostMove;
7203                 /* look for last irreversble move */
7204                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7205                     count--;
7206                 /* if we hit starting position, add initial plies */
7207                 if( count == backwardMostMove )
7208                     count -= initialRulePlies;
7209                 count = forwardMostMove - count;
7210                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7211                         // adjust reversible move counter for checks in Xiangqi
7212                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7213                         if(i < backwardMostMove) i = backwardMostMove;
7214                         while(i <= forwardMostMove) {
7215                                 lastCheck = inCheck; // check evasion does not count
7216                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7217                                 if(inCheck || lastCheck) count--; // check does not count
7218                                 i++;
7219                         }
7220                 }
7221                 if( count >= 100)
7222                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7223                          /* this is used to judge if draw claims are legal */
7224                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7225                          if(engineOpponent) {
7226                            SendToProgram("force\n", engineOpponent); // suppress reply
7227                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7228                          }
7229                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7230                          return 1;
7231                 }
7232
7233                 /* if draw offer is pending, treat it as a draw claim
7234                  * when draw condition present, to allow engines a way to
7235                  * claim draws before making their move to avoid a race
7236                  * condition occurring after their move
7237                  */
7238                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7239                          char *p = NULL;
7240                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7241                              p = "Draw claim: 50-move rule";
7242                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7243                              p = "Draw claim: 3-fold repetition";
7244                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7245                              p = "Draw claim: insufficient mating material";
7246                          if( p != NULL && canAdjudicate) {
7247                              if(engineOpponent) {
7248                                SendToProgram("force\n", engineOpponent); // suppress reply
7249                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7250                              }
7251                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7252                              return 1;
7253                          }
7254                 }
7255
7256                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7257                     if(engineOpponent) {
7258                       SendToProgram("force\n", engineOpponent); // suppress reply
7259                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7260                     }
7261                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7262                     return 1;
7263                 }
7264         return 0;
7265 }
7266
7267 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7268 {   // [HGM] book: this routine intercepts moves to simulate book replies
7269     char *bookHit = NULL;
7270
7271     //first determine if the incoming move brings opponent into his book
7272     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7273         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7274     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7275     if(bookHit != NULL && !cps->bookSuspend) {
7276         // make sure opponent is not going to reply after receiving move to book position
7277         SendToProgram("force\n", cps);
7278         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7279     }
7280     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7281     // now arrange restart after book miss
7282     if(bookHit) {
7283         // after a book hit we never send 'go', and the code after the call to this routine
7284         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7285         char buf[MSG_SIZ];
7286         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7287         SendToProgram(buf, cps);
7288         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7289     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7290         SendToProgram("go\n", cps);
7291         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7292     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7293         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7294             SendToProgram("go\n", cps);
7295         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7296     }
7297     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7298 }
7299
7300 char *savedMessage;
7301 ChessProgramState *savedState;
7302 void DeferredBookMove(void)
7303 {
7304         if(savedState->lastPing != savedState->lastPong)
7305                     ScheduleDelayedEvent(DeferredBookMove, 10);
7306         else
7307         HandleMachineMove(savedMessage, savedState);
7308 }
7309
7310 void
7311 HandleMachineMove(message, cps)
7312      char *message;
7313      ChessProgramState *cps;
7314 {
7315     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7316     char realname[MSG_SIZ];
7317     int fromX, fromY, toX, toY;
7318     ChessMove moveType;
7319     char promoChar;
7320     char *p;
7321     int machineWhite;
7322     char *bookHit;
7323
7324     cps->userError = 0;
7325
7326 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7327     /*
7328      * Kludge to ignore BEL characters
7329      */
7330     while (*message == '\007') message++;
7331
7332     /*
7333      * [HGM] engine debug message: ignore lines starting with '#' character
7334      */
7335     if(cps->debug && *message == '#') return;
7336
7337     /*
7338      * Look for book output
7339      */
7340     if (cps == &first && bookRequested) {
7341         if (message[0] == '\t' || message[0] == ' ') {
7342             /* Part of the book output is here; append it */
7343             strcat(bookOutput, message);
7344             strcat(bookOutput, "  \n");
7345             return;
7346         } else if (bookOutput[0] != NULLCHAR) {
7347             /* All of book output has arrived; display it */
7348             char *p = bookOutput;
7349             while (*p != NULLCHAR) {
7350                 if (*p == '\t') *p = ' ';
7351                 p++;
7352             }
7353             DisplayInformation(bookOutput);
7354             bookRequested = FALSE;
7355             /* Fall through to parse the current output */
7356         }
7357     }
7358
7359     /*
7360      * Look for machine move.
7361      */
7362     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7363         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7364     {
7365         /* This method is only useful on engines that support ping */
7366         if (cps->lastPing != cps->lastPong) {
7367           if (gameMode == BeginningOfGame) {
7368             /* Extra move from before last new; ignore */
7369             if (appData.debugMode) {
7370                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7371             }
7372           } else {
7373             if (appData.debugMode) {
7374                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7375                         cps->which, gameMode);
7376             }
7377
7378             SendToProgram("undo\n", cps);
7379           }
7380           return;
7381         }
7382
7383         switch (gameMode) {
7384           case BeginningOfGame:
7385             /* Extra move from before last reset; ignore */
7386             if (appData.debugMode) {
7387                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7388             }
7389             return;
7390
7391           case EndOfGame:
7392           case IcsIdle:
7393           default:
7394             /* Extra move after we tried to stop.  The mode test is
7395                not a reliable way of detecting this problem, but it's
7396                the best we can do on engines that don't support ping.
7397             */
7398             if (appData.debugMode) {
7399                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7400                         cps->which, gameMode);
7401             }
7402             SendToProgram("undo\n", cps);
7403             return;
7404
7405           case MachinePlaysWhite:
7406           case IcsPlayingWhite:
7407             machineWhite = TRUE;
7408             break;
7409
7410           case MachinePlaysBlack:
7411           case IcsPlayingBlack:
7412             machineWhite = FALSE;
7413             break;
7414
7415           case TwoMachinesPlay:
7416             machineWhite = (cps->twoMachinesColor[0] == 'w');
7417             break;
7418         }
7419         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7420             if (appData.debugMode) {
7421                 fprintf(debugFP,
7422                         "Ignoring move out of turn by %s, gameMode %d"
7423                         ", forwardMost %d\n",
7424                         cps->which, gameMode, forwardMostMove);
7425             }
7426             return;
7427         }
7428
7429     if (appData.debugMode) { int f = forwardMostMove;
7430         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7431                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7432                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7433     }
7434         if(cps->alphaRank) AlphaRank(machineMove, 4);
7435         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7436                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7437             /* Machine move could not be parsed; ignore it. */
7438           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7439                     machineMove, _(cps->which));
7440             DisplayError(buf1, 0);
7441             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7442                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7443             if (gameMode == TwoMachinesPlay) {
7444               GameEnds(machineWhite ? BlackWins : WhiteWins,
7445                        buf1, GE_XBOARD);
7446             }
7447             return;
7448         }
7449
7450         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7451         /* So we have to redo legality test with true e.p. status here,  */
7452         /* to make sure an illegal e.p. capture does not slip through,   */
7453         /* to cause a forfeit on a justified illegal-move complaint      */
7454         /* of the opponent.                                              */
7455         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7456            ChessMove moveType;
7457            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7458                              fromY, fromX, toY, toX, promoChar);
7459             if (appData.debugMode) {
7460                 int i;
7461                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7462                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7463                 fprintf(debugFP, "castling rights\n");
7464             }
7465             if(moveType == IllegalMove) {
7466               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7467                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7468                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7469                            buf1, GE_XBOARD);
7470                 return;
7471            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7472            /* [HGM] Kludge to handle engines that send FRC-style castling
7473               when they shouldn't (like TSCP-Gothic) */
7474            switch(moveType) {
7475              case WhiteASideCastleFR:
7476              case BlackASideCastleFR:
7477                toX+=2;
7478                currentMoveString[2]++;
7479                break;
7480              case WhiteHSideCastleFR:
7481              case BlackHSideCastleFR:
7482                toX--;
7483                currentMoveString[2]--;
7484                break;
7485              default: ; // nothing to do, but suppresses warning of pedantic compilers
7486            }
7487         }
7488         hintRequested = FALSE;
7489         lastHint[0] = NULLCHAR;
7490         bookRequested = FALSE;
7491         /* Program may be pondering now */
7492         cps->maybeThinking = TRUE;
7493         if (cps->sendTime == 2) cps->sendTime = 1;
7494         if (cps->offeredDraw) cps->offeredDraw--;
7495
7496         /* [AS] Save move info*/
7497         pvInfoList[ forwardMostMove ].score = programStats.score;
7498         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7499         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7500
7501         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7502
7503         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7504         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7505             int count = 0;
7506
7507             while( count < adjudicateLossPlies ) {
7508                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7509
7510                 if( count & 1 ) {
7511                     score = -score; /* Flip score for winning side */
7512                 }
7513
7514                 if( score > adjudicateLossThreshold ) {
7515                     break;
7516                 }
7517
7518                 count++;
7519             }
7520
7521             if( count >= adjudicateLossPlies ) {
7522                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7523
7524                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7525                     "Xboard adjudication",
7526                     GE_XBOARD );
7527
7528                 return;
7529             }
7530         }
7531
7532         if(Adjudicate(cps)) {
7533             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7534             return; // [HGM] adjudicate: for all automatic game ends
7535         }
7536
7537 #if ZIPPY
7538         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7539             first.initDone) {
7540           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7541                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7542                 SendToICS("draw ");
7543                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7544           }
7545           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7546           ics_user_moved = 1;
7547           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7548                 char buf[3*MSG_SIZ];
7549
7550                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7551                         programStats.score / 100.,
7552                         programStats.depth,
7553                         programStats.time / 100.,
7554                         (unsigned int)programStats.nodes,
7555                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7556                         programStats.movelist);
7557                 SendToICS(buf);
7558 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7559           }
7560         }
7561 #endif
7562
7563         /* [AS] Clear stats for next move */
7564         ClearProgramStats();
7565         thinkOutput[0] = NULLCHAR;
7566         hiddenThinkOutputState = 0;
7567
7568         bookHit = NULL;
7569         if (gameMode == TwoMachinesPlay) {
7570             /* [HGM] relaying draw offers moved to after reception of move */
7571             /* and interpreting offer as claim if it brings draw condition */
7572             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7573                 SendToProgram("draw\n", cps->other);
7574             }
7575             if (cps->other->sendTime) {
7576                 SendTimeRemaining(cps->other,
7577                                   cps->other->twoMachinesColor[0] == 'w');
7578             }
7579             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7580             if (firstMove && !bookHit) {
7581                 firstMove = FALSE;
7582                 if (cps->other->useColors) {
7583                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7584                 }
7585                 SendToProgram("go\n", cps->other);
7586             }
7587             cps->other->maybeThinking = TRUE;
7588         }
7589
7590         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7591
7592         if (!pausing && appData.ringBellAfterMoves) {
7593             RingBell();
7594         }
7595
7596         /*
7597          * Reenable menu items that were disabled while
7598          * machine was thinking
7599          */
7600         if (gameMode != TwoMachinesPlay)
7601             SetUserThinkingEnables();
7602
7603         // [HGM] book: after book hit opponent has received move and is now in force mode
7604         // force the book reply into it, and then fake that it outputted this move by jumping
7605         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7606         if(bookHit) {
7607                 static char bookMove[MSG_SIZ]; // a bit generous?
7608
7609                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7610                 strcat(bookMove, bookHit);
7611                 message = bookMove;
7612                 cps = cps->other;
7613                 programStats.nodes = programStats.depth = programStats.time =
7614                 programStats.score = programStats.got_only_move = 0;
7615                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7616
7617                 if(cps->lastPing != cps->lastPong) {
7618                     savedMessage = message; // args for deferred call
7619                     savedState = cps;
7620                     ScheduleDelayedEvent(DeferredBookMove, 10);
7621                     return;
7622                 }
7623                 goto FakeBookMove;
7624         }
7625
7626         return;
7627     }
7628
7629     /* Set special modes for chess engines.  Later something general
7630      *  could be added here; for now there is just one kludge feature,
7631      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7632      *  when "xboard" is given as an interactive command.
7633      */
7634     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7635         cps->useSigint = FALSE;
7636         cps->useSigterm = FALSE;
7637     }
7638     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7639       ParseFeatures(message+8, cps);
7640       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7641     }
7642
7643     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7644       int dummy, s=6; char buf[MSG_SIZ];
7645       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7646       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7647       ParseFEN(boards[0], &dummy, message+s);
7648       DrawPosition(TRUE, boards[0]);
7649       startedFromSetupPosition = TRUE;
7650       return;
7651     }
7652     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7653      * want this, I was asked to put it in, and obliged.
7654      */
7655     if (!strncmp(message, "setboard ", 9)) {
7656         Board initial_position;
7657
7658         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7659
7660         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7661             DisplayError(_("Bad FEN received from engine"), 0);
7662             return ;
7663         } else {
7664            Reset(TRUE, FALSE);
7665            CopyBoard(boards[0], initial_position);
7666            initialRulePlies = FENrulePlies;
7667            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7668            else gameMode = MachinePlaysBlack;
7669            DrawPosition(FALSE, boards[currentMove]);
7670         }
7671         return;
7672     }
7673
7674     /*
7675      * Look for communication commands
7676      */
7677     if (!strncmp(message, "telluser ", 9)) {
7678         if(message[9] == '\\' && message[10] == '\\')
7679             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7680         DisplayNote(message + 9);
7681         return;
7682     }
7683     if (!strncmp(message, "tellusererror ", 14)) {
7684         cps->userError = 1;
7685         if(message[14] == '\\' && message[15] == '\\')
7686             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7687         DisplayError(message + 14, 0);
7688         return;
7689     }
7690     if (!strncmp(message, "tellopponent ", 13)) {
7691       if (appData.icsActive) {
7692         if (loggedOn) {
7693           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7694           SendToICS(buf1);
7695         }
7696       } else {
7697         DisplayNote(message + 13);
7698       }
7699       return;
7700     }
7701     if (!strncmp(message, "tellothers ", 11)) {
7702       if (appData.icsActive) {
7703         if (loggedOn) {
7704           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7705           SendToICS(buf1);
7706         }
7707       }
7708       return;
7709     }
7710     if (!strncmp(message, "tellall ", 8)) {
7711       if (appData.icsActive) {
7712         if (loggedOn) {
7713           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7714           SendToICS(buf1);
7715         }
7716       } else {
7717         DisplayNote(message + 8);
7718       }
7719       return;
7720     }
7721     if (strncmp(message, "warning", 7) == 0) {
7722         /* Undocumented feature, use tellusererror in new code */
7723         DisplayError(message, 0);
7724         return;
7725     }
7726     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7727         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7728         strcat(realname, " query");
7729         AskQuestion(realname, buf2, buf1, cps->pr);
7730         return;
7731     }
7732     /* Commands from the engine directly to ICS.  We don't allow these to be
7733      *  sent until we are logged on. Crafty kibitzes have been known to
7734      *  interfere with the login process.
7735      */
7736     if (loggedOn) {
7737         if (!strncmp(message, "tellics ", 8)) {
7738             SendToICS(message + 8);
7739             SendToICS("\n");
7740             return;
7741         }
7742         if (!strncmp(message, "tellicsnoalias ", 15)) {
7743             SendToICS(ics_prefix);
7744             SendToICS(message + 15);
7745             SendToICS("\n");
7746             return;
7747         }
7748         /* The following are for backward compatibility only */
7749         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7750             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7751             SendToICS(ics_prefix);
7752             SendToICS(message);
7753             SendToICS("\n");
7754             return;
7755         }
7756     }
7757     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7758         return;
7759     }
7760     /*
7761      * If the move is illegal, cancel it and redraw the board.
7762      * Also deal with other error cases.  Matching is rather loose
7763      * here to accommodate engines written before the spec.
7764      */
7765     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7766         strncmp(message, "Error", 5) == 0) {
7767         if (StrStr(message, "name") ||
7768             StrStr(message, "rating") || StrStr(message, "?") ||
7769             StrStr(message, "result") || StrStr(message, "board") ||
7770             StrStr(message, "bk") || StrStr(message, "computer") ||
7771             StrStr(message, "variant") || StrStr(message, "hint") ||
7772             StrStr(message, "random") || StrStr(message, "depth") ||
7773             StrStr(message, "accepted")) {
7774             return;
7775         }
7776         if (StrStr(message, "protover")) {
7777           /* Program is responding to input, so it's apparently done
7778              initializing, and this error message indicates it is
7779              protocol version 1.  So we don't need to wait any longer
7780              for it to initialize and send feature commands. */
7781           FeatureDone(cps, 1);
7782           cps->protocolVersion = 1;
7783           return;
7784         }
7785         cps->maybeThinking = FALSE;
7786
7787         if (StrStr(message, "draw")) {
7788             /* Program doesn't have "draw" command */
7789             cps->sendDrawOffers = 0;
7790             return;
7791         }
7792         if (cps->sendTime != 1 &&
7793             (StrStr(message, "time") || StrStr(message, "otim"))) {
7794           /* Program apparently doesn't have "time" or "otim" command */
7795           cps->sendTime = 0;
7796           return;
7797         }
7798         if (StrStr(message, "analyze")) {
7799             cps->analysisSupport = FALSE;
7800             cps->analyzing = FALSE;
7801             Reset(FALSE, TRUE);
7802             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7803             DisplayError(buf2, 0);
7804             return;
7805         }
7806         if (StrStr(message, "(no matching move)st")) {
7807           /* Special kludge for GNU Chess 4 only */
7808           cps->stKludge = TRUE;
7809           SendTimeControl(cps, movesPerSession, timeControl,
7810                           timeIncrement, appData.searchDepth,
7811                           searchTime);
7812           return;
7813         }
7814         if (StrStr(message, "(no matching move)sd")) {
7815           /* Special kludge for GNU Chess 4 only */
7816           cps->sdKludge = TRUE;
7817           SendTimeControl(cps, movesPerSession, timeControl,
7818                           timeIncrement, appData.searchDepth,
7819                           searchTime);
7820           return;
7821         }
7822         if (!StrStr(message, "llegal")) {
7823             return;
7824         }
7825         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7826             gameMode == IcsIdle) return;
7827         if (forwardMostMove <= backwardMostMove) return;
7828         if (pausing) PauseEvent();
7829       if(appData.forceIllegal) {
7830             // [HGM] illegal: machine refused move; force position after move into it
7831           SendToProgram("force\n", cps);
7832           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7833                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7834                 // when black is to move, while there might be nothing on a2 or black
7835                 // might already have the move. So send the board as if white has the move.
7836                 // But first we must change the stm of the engine, as it refused the last move
7837                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7838                 if(WhiteOnMove(forwardMostMove)) {
7839                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7840                     SendBoard(cps, forwardMostMove); // kludgeless board
7841                 } else {
7842                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7843                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7844                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7845                 }
7846           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7847             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7848                  gameMode == TwoMachinesPlay)
7849               SendToProgram("go\n", cps);
7850             return;
7851       } else
7852         if (gameMode == PlayFromGameFile) {
7853             /* Stop reading this game file */
7854             gameMode = EditGame;
7855             ModeHighlight();
7856         }
7857         /* [HGM] illegal-move claim should forfeit game when Xboard */
7858         /* only passes fully legal moves                            */
7859         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7860             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7861                                 "False illegal-move claim", GE_XBOARD );
7862             return; // do not take back move we tested as valid
7863         }
7864         currentMove = forwardMostMove-1;
7865         DisplayMove(currentMove-1); /* before DisplayMoveError */
7866         SwitchClocks(forwardMostMove-1); // [HGM] race
7867         DisplayBothClocks();
7868         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7869                 parseList[currentMove], _(cps->which));
7870         DisplayMoveError(buf1);
7871         DrawPosition(FALSE, boards[currentMove]);
7872         return;
7873     }
7874     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7875         /* Program has a broken "time" command that
7876            outputs a string not ending in newline.
7877            Don't use it. */
7878         cps->sendTime = 0;
7879     }
7880
7881     /*
7882      * If chess program startup fails, exit with an error message.
7883      * Attempts to recover here are futile.
7884      */
7885     if ((StrStr(message, "unknown host") != NULL)
7886         || (StrStr(message, "No remote directory") != NULL)
7887         || (StrStr(message, "not found") != NULL)
7888         || (StrStr(message, "No such file") != NULL)
7889         || (StrStr(message, "can't alloc") != NULL)
7890         || (StrStr(message, "Permission denied") != NULL)) {
7891
7892         cps->maybeThinking = FALSE;
7893         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7894                 _(cps->which), cps->program, cps->host, message);
7895         RemoveInputSource(cps->isr);
7896         DisplayFatalError(buf1, 0, 1);
7897         return;
7898     }
7899
7900     /*
7901      * Look for hint output
7902      */
7903     if (sscanf(message, "Hint: %s", buf1) == 1) {
7904         if (cps == &first && hintRequested) {
7905             hintRequested = FALSE;
7906             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7907                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7908                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7909                                     PosFlags(forwardMostMove),
7910                                     fromY, fromX, toY, toX, promoChar, buf1);
7911                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7912                 DisplayInformation(buf2);
7913             } else {
7914                 /* Hint move could not be parsed!? */
7915               snprintf(buf2, sizeof(buf2),
7916                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7917                         buf1, _(cps->which));
7918                 DisplayError(buf2, 0);
7919             }
7920         } else {
7921           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7922         }
7923         return;
7924     }
7925
7926     /*
7927      * Ignore other messages if game is not in progress
7928      */
7929     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7930         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7931
7932     /*
7933      * look for win, lose, draw, or draw offer
7934      */
7935     if (strncmp(message, "1-0", 3) == 0) {
7936         char *p, *q, *r = "";
7937         p = strchr(message, '{');
7938         if (p) {
7939             q = strchr(p, '}');
7940             if (q) {
7941                 *q = NULLCHAR;
7942                 r = p + 1;
7943             }
7944         }
7945         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7946         return;
7947     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
7958         if (strcmp(r, "Black resigns") == 0) {
7959             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7960             return;
7961         }
7962         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7963         return;
7964     } else if (strncmp(message, "1/2", 3) == 0) {
7965         char *p, *q, *r = "";
7966         p = strchr(message, '{');
7967         if (p) {
7968             q = strchr(p, '}');
7969             if (q) {
7970                 *q = NULLCHAR;
7971                 r = p + 1;
7972             }
7973         }
7974
7975         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7976         return;
7977
7978     } else if (strncmp(message, "White resign", 12) == 0) {
7979         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7980         return;
7981     } else if (strncmp(message, "Black resign", 12) == 0) {
7982         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7983         return;
7984     } else if (strncmp(message, "White matches", 13) == 0 ||
7985                strncmp(message, "Black matches", 13) == 0   ) {
7986         /* [HGM] ignore GNUShogi noises */
7987         return;
7988     } else if (strncmp(message, "White", 5) == 0 &&
7989                message[5] != '(' &&
7990                StrStr(message, "Black") == NULL) {
7991         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7992         return;
7993     } else if (strncmp(message, "Black", 5) == 0 &&
7994                message[5] != '(') {
7995         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7996         return;
7997     } else if (strcmp(message, "resign") == 0 ||
7998                strcmp(message, "computer resigns") == 0) {
7999         switch (gameMode) {
8000           case MachinePlaysBlack:
8001           case IcsPlayingBlack:
8002             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8003             break;
8004           case MachinePlaysWhite:
8005           case IcsPlayingWhite:
8006             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8007             break;
8008           case TwoMachinesPlay:
8009             if (cps->twoMachinesColor[0] == 'w')
8010               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8011             else
8012               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8013             break;
8014           default:
8015             /* can't happen */
8016             break;
8017         }
8018         return;
8019     } else if (strncmp(message, "opponent mates", 14) == 0) {
8020         switch (gameMode) {
8021           case MachinePlaysBlack:
8022           case IcsPlayingBlack:
8023             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8024             break;
8025           case MachinePlaysWhite:
8026           case IcsPlayingWhite:
8027             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8028             break;
8029           case TwoMachinesPlay:
8030             if (cps->twoMachinesColor[0] == 'w')
8031               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8032             else
8033               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8034             break;
8035           default:
8036             /* can't happen */
8037             break;
8038         }
8039         return;
8040     } else if (strncmp(message, "computer mates", 14) == 0) {
8041         switch (gameMode) {
8042           case MachinePlaysBlack:
8043           case IcsPlayingBlack:
8044             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8045             break;
8046           case MachinePlaysWhite:
8047           case IcsPlayingWhite:
8048             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8049             break;
8050           case TwoMachinesPlay:
8051             if (cps->twoMachinesColor[0] == 'w')
8052               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8053             else
8054               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8055             break;
8056           default:
8057             /* can't happen */
8058             break;
8059         }
8060         return;
8061     } else if (strncmp(message, "checkmate", 9) == 0) {
8062         if (WhiteOnMove(forwardMostMove)) {
8063             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8064         } else {
8065             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8066         }
8067         return;
8068     } else if (strstr(message, "Draw") != NULL ||
8069                strstr(message, "game is a draw") != NULL) {
8070         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8071         return;
8072     } else if (strstr(message, "offer") != NULL &&
8073                strstr(message, "draw") != NULL) {
8074 #if ZIPPY
8075         if (appData.zippyPlay && first.initDone) {
8076             /* Relay offer to ICS */
8077             SendToICS(ics_prefix);
8078             SendToICS("draw\n");
8079         }
8080 #endif
8081         cps->offeredDraw = 2; /* valid until this engine moves twice */
8082         if (gameMode == TwoMachinesPlay) {
8083             if (cps->other->offeredDraw) {
8084                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8085             /* [HGM] in two-machine mode we delay relaying draw offer      */
8086             /* until after we also have move, to see if it is really claim */
8087             }
8088         } else if (gameMode == MachinePlaysWhite ||
8089                    gameMode == MachinePlaysBlack) {
8090           if (userOfferedDraw) {
8091             DisplayInformation(_("Machine accepts your draw offer"));
8092             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8093           } else {
8094             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8095           }
8096         }
8097     }
8098
8099
8100     /*
8101      * Look for thinking output
8102      */
8103     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8104           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8105                                 ) {
8106         int plylev, mvleft, mvtot, curscore, time;
8107         char mvname[MOVE_LEN];
8108         u64 nodes; // [DM]
8109         char plyext;
8110         int ignore = FALSE;
8111         int prefixHint = FALSE;
8112         mvname[0] = NULLCHAR;
8113
8114         switch (gameMode) {
8115           case MachinePlaysBlack:
8116           case IcsPlayingBlack:
8117             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8118             break;
8119           case MachinePlaysWhite:
8120           case IcsPlayingWhite:
8121             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8122             break;
8123           case AnalyzeMode:
8124           case AnalyzeFile:
8125             break;
8126           case IcsObserving: /* [DM] icsEngineAnalyze */
8127             if (!appData.icsEngineAnalyze) ignore = TRUE;
8128             break;
8129           case TwoMachinesPlay:
8130             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8131                 ignore = TRUE;
8132             }
8133             break;
8134           default:
8135             ignore = TRUE;
8136             break;
8137         }
8138
8139         if (!ignore) {
8140             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8141             buf1[0] = NULLCHAR;
8142             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8143                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8144
8145                 if (plyext != ' ' && plyext != '\t') {
8146                     time *= 100;
8147                 }
8148
8149                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8150                 if( cps->scoreIsAbsolute &&
8151                     ( gameMode == MachinePlaysBlack ||
8152                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8153                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8154                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8155                      !WhiteOnMove(currentMove)
8156                     ) )
8157                 {
8158                     curscore = -curscore;
8159                 }
8160
8161
8162                 tempStats.depth = plylev;
8163                 tempStats.nodes = nodes;
8164                 tempStats.time = time;
8165                 tempStats.score = curscore;
8166                 tempStats.got_only_move = 0;
8167
8168                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8169                         int ticklen;
8170
8171                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8172                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8173                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8174                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8175                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8176                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8177                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8178                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8179                 }
8180
8181                 /* Buffer overflow protection */
8182                 if (buf1[0] != NULLCHAR) {
8183                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8184                         && appData.debugMode) {
8185                         fprintf(debugFP,
8186                                 "PV is too long; using the first %u bytes.\n",
8187                                 (unsigned) sizeof(tempStats.movelist) - 1);
8188                     }
8189
8190                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8191                 } else {
8192                     sprintf(tempStats.movelist, " no PV\n");
8193                 }
8194
8195                 if (tempStats.seen_stat) {
8196                     tempStats.ok_to_send = 1;
8197                 }
8198
8199                 if (strchr(tempStats.movelist, '(') != NULL) {
8200                     tempStats.line_is_book = 1;
8201                     tempStats.nr_moves = 0;
8202                     tempStats.moves_left = 0;
8203                 } else {
8204                     tempStats.line_is_book = 0;
8205                 }
8206
8207                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8208                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8209
8210                 SendProgramStatsToFrontend( cps, &tempStats );
8211
8212                 /*
8213                     [AS] Protect the thinkOutput buffer from overflow... this
8214                     is only useful if buf1 hasn't overflowed first!
8215                 */
8216                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8217                          plylev,
8218                          (gameMode == TwoMachinesPlay ?
8219                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8220                          ((double) curscore) / 100.0,
8221                          prefixHint ? lastHint : "",
8222                          prefixHint ? " " : "" );
8223
8224                 if( buf1[0] != NULLCHAR ) {
8225                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8226
8227                     if( strlen(buf1) > max_len ) {
8228                         if( appData.debugMode) {
8229                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8230                         }
8231                         buf1[max_len+1] = '\0';
8232                     }
8233
8234                     strcat( thinkOutput, buf1 );
8235                 }
8236
8237                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8238                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8239                     DisplayMove(currentMove - 1);
8240                 }
8241                 return;
8242
8243             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8244                 /* crafty (9.25+) says "(only move) <move>"
8245                  * if there is only 1 legal move
8246                  */
8247                 sscanf(p, "(only move) %s", buf1);
8248                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8249                 sprintf(programStats.movelist, "%s (only move)", buf1);
8250                 programStats.depth = 1;
8251                 programStats.nr_moves = 1;
8252                 programStats.moves_left = 1;
8253                 programStats.nodes = 1;
8254                 programStats.time = 1;
8255                 programStats.got_only_move = 1;
8256
8257                 /* Not really, but we also use this member to
8258                    mean "line isn't going to change" (Crafty
8259                    isn't searching, so stats won't change) */
8260                 programStats.line_is_book = 1;
8261
8262                 SendProgramStatsToFrontend( cps, &programStats );
8263
8264                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8265                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8266                     DisplayMove(currentMove - 1);
8267                 }
8268                 return;
8269             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8270                               &time, &nodes, &plylev, &mvleft,
8271                               &mvtot, mvname) >= 5) {
8272                 /* The stat01: line is from Crafty (9.29+) in response
8273                    to the "." command */
8274                 programStats.seen_stat = 1;
8275                 cps->maybeThinking = TRUE;
8276
8277                 if (programStats.got_only_move || !appData.periodicUpdates)
8278                   return;
8279
8280                 programStats.depth = plylev;
8281                 programStats.time = time;
8282                 programStats.nodes = nodes;
8283                 programStats.moves_left = mvleft;
8284                 programStats.nr_moves = mvtot;
8285                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8286                 programStats.ok_to_send = 1;
8287                 programStats.movelist[0] = '\0';
8288
8289                 SendProgramStatsToFrontend( cps, &programStats );
8290
8291                 return;
8292
8293             } else if (strncmp(message,"++",2) == 0) {
8294                 /* Crafty 9.29+ outputs this */
8295                 programStats.got_fail = 2;
8296                 return;
8297
8298             } else if (strncmp(message,"--",2) == 0) {
8299                 /* Crafty 9.29+ outputs this */
8300                 programStats.got_fail = 1;
8301                 return;
8302
8303             } else if (thinkOutput[0] != NULLCHAR &&
8304                        strncmp(message, "    ", 4) == 0) {
8305                 unsigned message_len;
8306
8307                 p = message;
8308                 while (*p && *p == ' ') p++;
8309
8310                 message_len = strlen( p );
8311
8312                 /* [AS] Avoid buffer overflow */
8313                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8314                     strcat(thinkOutput, " ");
8315                     strcat(thinkOutput, p);
8316                 }
8317
8318                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8319                     strcat(programStats.movelist, " ");
8320                     strcat(programStats.movelist, p);
8321                 }
8322
8323                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8324                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8325                     DisplayMove(currentMove - 1);
8326                 }
8327                 return;
8328             }
8329         }
8330         else {
8331             buf1[0] = NULLCHAR;
8332
8333             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8334                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8335             {
8336                 ChessProgramStats cpstats;
8337
8338                 if (plyext != ' ' && plyext != '\t') {
8339                     time *= 100;
8340                 }
8341
8342                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8343                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8344                     curscore = -curscore;
8345                 }
8346
8347                 cpstats.depth = plylev;
8348                 cpstats.nodes = nodes;
8349                 cpstats.time = time;
8350                 cpstats.score = curscore;
8351                 cpstats.got_only_move = 0;
8352                 cpstats.movelist[0] = '\0';
8353
8354                 if (buf1[0] != NULLCHAR) {
8355                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8356                 }
8357
8358                 cpstats.ok_to_send = 0;
8359                 cpstats.line_is_book = 0;
8360                 cpstats.nr_moves = 0;
8361                 cpstats.moves_left = 0;
8362
8363                 SendProgramStatsToFrontend( cps, &cpstats );
8364             }
8365         }
8366     }
8367 }
8368
8369
8370 /* Parse a game score from the character string "game", and
8371    record it as the history of the current game.  The game
8372    score is NOT assumed to start from the standard position.
8373    The display is not updated in any way.
8374    */
8375 void
8376 ParseGameHistory(game)
8377      char *game;
8378 {
8379     ChessMove moveType;
8380     int fromX, fromY, toX, toY, boardIndex;
8381     char promoChar;
8382     char *p, *q;
8383     char buf[MSG_SIZ];
8384
8385     if (appData.debugMode)
8386       fprintf(debugFP, "Parsing game history: %s\n", game);
8387
8388     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8389     gameInfo.site = StrSave(appData.icsHost);
8390     gameInfo.date = PGNDate();
8391     gameInfo.round = StrSave("-");
8392
8393     /* Parse out names of players */
8394     while (*game == ' ') game++;
8395     p = buf;
8396     while (*game != ' ') *p++ = *game++;
8397     *p = NULLCHAR;
8398     gameInfo.white = StrSave(buf);
8399     while (*game == ' ') game++;
8400     p = buf;
8401     while (*game != ' ' && *game != '\n') *p++ = *game++;
8402     *p = NULLCHAR;
8403     gameInfo.black = StrSave(buf);
8404
8405     /* Parse moves */
8406     boardIndex = blackPlaysFirst ? 1 : 0;
8407     yynewstr(game);
8408     for (;;) {
8409         yyboardindex = boardIndex;
8410         moveType = (ChessMove) Myylex();
8411         switch (moveType) {
8412           case IllegalMove:             /* maybe suicide chess, etc. */
8413   if (appData.debugMode) {
8414     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8415     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8416     setbuf(debugFP, NULL);
8417   }
8418           case WhitePromotion:
8419           case BlackPromotion:
8420           case WhiteNonPromotion:
8421           case BlackNonPromotion:
8422           case NormalMove:
8423           case WhiteCapturesEnPassant:
8424           case BlackCapturesEnPassant:
8425           case WhiteKingSideCastle:
8426           case WhiteQueenSideCastle:
8427           case BlackKingSideCastle:
8428           case BlackQueenSideCastle:
8429           case WhiteKingSideCastleWild:
8430           case WhiteQueenSideCastleWild:
8431           case BlackKingSideCastleWild:
8432           case BlackQueenSideCastleWild:
8433           /* PUSH Fabien */
8434           case WhiteHSideCastleFR:
8435           case WhiteASideCastleFR:
8436           case BlackHSideCastleFR:
8437           case BlackASideCastleFR:
8438           /* POP Fabien */
8439             fromX = currentMoveString[0] - AAA;
8440             fromY = currentMoveString[1] - ONE;
8441             toX = currentMoveString[2] - AAA;
8442             toY = currentMoveString[3] - ONE;
8443             promoChar = currentMoveString[4];
8444             break;
8445           case WhiteDrop:
8446           case BlackDrop:
8447             fromX = moveType == WhiteDrop ?
8448               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8449             (int) CharToPiece(ToLower(currentMoveString[0]));
8450             fromY = DROP_RANK;
8451             toX = currentMoveString[2] - AAA;
8452             toY = currentMoveString[3] - ONE;
8453             promoChar = NULLCHAR;
8454             break;
8455           case AmbiguousMove:
8456             /* bug? */
8457             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8458   if (appData.debugMode) {
8459     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8460     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8461     setbuf(debugFP, NULL);
8462   }
8463             DisplayError(buf, 0);
8464             return;
8465           case ImpossibleMove:
8466             /* bug? */
8467             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8468   if (appData.debugMode) {
8469     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8470     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8471     setbuf(debugFP, NULL);
8472   }
8473             DisplayError(buf, 0);
8474             return;
8475           case EndOfFile:
8476             if (boardIndex < backwardMostMove) {
8477                 /* Oops, gap.  How did that happen? */
8478                 DisplayError(_("Gap in move list"), 0);
8479                 return;
8480             }
8481             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8482             if (boardIndex > forwardMostMove) {
8483                 forwardMostMove = boardIndex;
8484             }
8485             return;
8486           case ElapsedTime:
8487             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8488                 strcat(parseList[boardIndex-1], " ");
8489                 strcat(parseList[boardIndex-1], yy_text);
8490             }
8491             continue;
8492           case Comment:
8493           case PGNTag:
8494           case NAG:
8495           default:
8496             /* ignore */
8497             continue;
8498           case WhiteWins:
8499           case BlackWins:
8500           case GameIsDrawn:
8501           case GameUnfinished:
8502             if (gameMode == IcsExamining) {
8503                 if (boardIndex < backwardMostMove) {
8504                     /* Oops, gap.  How did that happen? */
8505                     return;
8506                 }
8507                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8508                 return;
8509             }
8510             gameInfo.result = moveType;
8511             p = strchr(yy_text, '{');
8512             if (p == NULL) p = strchr(yy_text, '(');
8513             if (p == NULL) {
8514                 p = yy_text;
8515                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8516             } else {
8517                 q = strchr(p, *p == '{' ? '}' : ')');
8518                 if (q != NULL) *q = NULLCHAR;
8519                 p++;
8520             }
8521             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8522             gameInfo.resultDetails = StrSave(p);
8523             continue;
8524         }
8525         if (boardIndex >= forwardMostMove &&
8526             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8527             backwardMostMove = blackPlaysFirst ? 1 : 0;
8528             return;
8529         }
8530         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8531                                  fromY, fromX, toY, toX, promoChar,
8532                                  parseList[boardIndex]);
8533         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8534         /* currentMoveString is set as a side-effect of yylex */
8535         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8536         strcat(moveList[boardIndex], "\n");
8537         boardIndex++;
8538         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8539         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8540           case MT_NONE:
8541           case MT_STALEMATE:
8542           default:
8543             break;
8544           case MT_CHECK:
8545             if(gameInfo.variant != VariantShogi)
8546                 strcat(parseList[boardIndex - 1], "+");
8547             break;
8548           case MT_CHECKMATE:
8549           case MT_STAINMATE:
8550             strcat(parseList[boardIndex - 1], "#");
8551             break;
8552         }
8553     }
8554 }
8555
8556
8557 /* Apply a move to the given board  */
8558 void
8559 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8560      int fromX, fromY, toX, toY;
8561      int promoChar;
8562      Board board;
8563 {
8564   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8565   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8566
8567     /* [HGM] compute & store e.p. status and castling rights for new position */
8568     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8569
8570       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8571       oldEP = (signed char)board[EP_STATUS];
8572       board[EP_STATUS] = EP_NONE;
8573
8574       if( board[toY][toX] != EmptySquare )
8575            board[EP_STATUS] = EP_CAPTURE;
8576
8577   if (fromY == DROP_RANK) {
8578         /* must be first */
8579         piece = board[toY][toX] = (ChessSquare) fromX;
8580   } else {
8581       int i;
8582
8583       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8584            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8585                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8586       } else
8587       if( board[fromY][fromX] == WhitePawn ) {
8588            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8589                board[EP_STATUS] = EP_PAWN_MOVE;
8590            if( toY-fromY==2) {
8591                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8592                         gameInfo.variant != VariantBerolina || toX < fromX)
8593                       board[EP_STATUS] = toX | berolina;
8594                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8595                         gameInfo.variant != VariantBerolina || toX > fromX)
8596                       board[EP_STATUS] = toX;
8597            }
8598       } else
8599       if( board[fromY][fromX] == BlackPawn ) {
8600            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8601                board[EP_STATUS] = EP_PAWN_MOVE;
8602            if( toY-fromY== -2) {
8603                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8604                         gameInfo.variant != VariantBerolina || toX < fromX)
8605                       board[EP_STATUS] = toX | berolina;
8606                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8607                         gameInfo.variant != VariantBerolina || toX > fromX)
8608                       board[EP_STATUS] = toX;
8609            }
8610        }
8611
8612        for(i=0; i<nrCastlingRights; i++) {
8613            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8614               board[CASTLING][i] == toX   && castlingRank[i] == toY
8615              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8616        }
8617
8618      if (fromX == toX && fromY == toY) return;
8619
8620      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8621      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8622      if(gameInfo.variant == VariantKnightmate)
8623          king += (int) WhiteUnicorn - (int) WhiteKing;
8624
8625     /* Code added by Tord: */
8626     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8627     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8628         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8629       board[fromY][fromX] = EmptySquare;
8630       board[toY][toX] = EmptySquare;
8631       if((toX > fromX) != (piece == WhiteRook)) {
8632         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8633       } else {
8634         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8635       }
8636     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8637                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8638       board[fromY][fromX] = EmptySquare;
8639       board[toY][toX] = EmptySquare;
8640       if((toX > fromX) != (piece == BlackRook)) {
8641         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8642       } else {
8643         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8644       }
8645     /* End of code added by Tord */
8646
8647     } else if (board[fromY][fromX] == king
8648         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8649         && toY == fromY && toX > fromX+1) {
8650         board[fromY][fromX] = EmptySquare;
8651         board[toY][toX] = king;
8652         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8653         board[fromY][BOARD_RGHT-1] = EmptySquare;
8654     } else if (board[fromY][fromX] == king
8655         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8656                && toY == fromY && toX < fromX-1) {
8657         board[fromY][fromX] = EmptySquare;
8658         board[toY][toX] = king;
8659         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8660         board[fromY][BOARD_LEFT] = EmptySquare;
8661     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8662                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8663                && toY >= BOARD_HEIGHT-promoRank
8664                ) {
8665         /* white pawn promotion */
8666         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8667         if (board[toY][toX] == EmptySquare) {
8668             board[toY][toX] = WhiteQueen;
8669         }
8670         if(gameInfo.variant==VariantBughouse ||
8671            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8672             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8673         board[fromY][fromX] = EmptySquare;
8674     } else if ((fromY == BOARD_HEIGHT-4)
8675                && (toX != fromX)
8676                && gameInfo.variant != VariantXiangqi
8677                && gameInfo.variant != VariantBerolina
8678                && (board[fromY][fromX] == WhitePawn)
8679                && (board[toY][toX] == EmptySquare)) {
8680         board[fromY][fromX] = EmptySquare;
8681         board[toY][toX] = WhitePawn;
8682         captured = board[toY - 1][toX];
8683         board[toY - 1][toX] = EmptySquare;
8684     } else if ((fromY == BOARD_HEIGHT-4)
8685                && (toX == fromX)
8686                && gameInfo.variant == VariantBerolina
8687                && (board[fromY][fromX] == WhitePawn)
8688                && (board[toY][toX] == EmptySquare)) {
8689         board[fromY][fromX] = EmptySquare;
8690         board[toY][toX] = WhitePawn;
8691         if(oldEP & EP_BEROLIN_A) {
8692                 captured = board[fromY][fromX-1];
8693                 board[fromY][fromX-1] = EmptySquare;
8694         }else{  captured = board[fromY][fromX+1];
8695                 board[fromY][fromX+1] = EmptySquare;
8696         }
8697     } else if (board[fromY][fromX] == king
8698         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8699                && toY == fromY && toX > fromX+1) {
8700         board[fromY][fromX] = EmptySquare;
8701         board[toY][toX] = king;
8702         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8703         board[fromY][BOARD_RGHT-1] = EmptySquare;
8704     } else if (board[fromY][fromX] == king
8705         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8706                && toY == fromY && toX < fromX-1) {
8707         board[fromY][fromX] = EmptySquare;
8708         board[toY][toX] = king;
8709         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8710         board[fromY][BOARD_LEFT] = EmptySquare;
8711     } else if (fromY == 7 && fromX == 3
8712                && board[fromY][fromX] == BlackKing
8713                && toY == 7 && toX == 5) {
8714         board[fromY][fromX] = EmptySquare;
8715         board[toY][toX] = BlackKing;
8716         board[fromY][7] = EmptySquare;
8717         board[toY][4] = BlackRook;
8718     } else if (fromY == 7 && fromX == 3
8719                && board[fromY][fromX] == BlackKing
8720                && toY == 7 && toX == 1) {
8721         board[fromY][fromX] = EmptySquare;
8722         board[toY][toX] = BlackKing;
8723         board[fromY][0] = EmptySquare;
8724         board[toY][2] = BlackRook;
8725     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8726                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8727                && toY < promoRank
8728                ) {
8729         /* black pawn promotion */
8730         board[toY][toX] = CharToPiece(ToLower(promoChar));
8731         if (board[toY][toX] == EmptySquare) {
8732             board[toY][toX] = BlackQueen;
8733         }
8734         if(gameInfo.variant==VariantBughouse ||
8735            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8736             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8737         board[fromY][fromX] = EmptySquare;
8738     } else if ((fromY == 3)
8739                && (toX != fromX)
8740                && gameInfo.variant != VariantXiangqi
8741                && gameInfo.variant != VariantBerolina
8742                && (board[fromY][fromX] == BlackPawn)
8743                && (board[toY][toX] == EmptySquare)) {
8744         board[fromY][fromX] = EmptySquare;
8745         board[toY][toX] = BlackPawn;
8746         captured = board[toY + 1][toX];
8747         board[toY + 1][toX] = EmptySquare;
8748     } else if ((fromY == 3)
8749                && (toX == fromX)
8750                && gameInfo.variant == VariantBerolina
8751                && (board[fromY][fromX] == BlackPawn)
8752                && (board[toY][toX] == EmptySquare)) {
8753         board[fromY][fromX] = EmptySquare;
8754         board[toY][toX] = BlackPawn;
8755         if(oldEP & EP_BEROLIN_A) {
8756                 captured = board[fromY][fromX-1];
8757                 board[fromY][fromX-1] = EmptySquare;
8758         }else{  captured = board[fromY][fromX+1];
8759                 board[fromY][fromX+1] = EmptySquare;
8760         }
8761     } else {
8762         board[toY][toX] = board[fromY][fromX];
8763         board[fromY][fromX] = EmptySquare;
8764     }
8765   }
8766
8767     if (gameInfo.holdingsWidth != 0) {
8768
8769       /* !!A lot more code needs to be written to support holdings  */
8770       /* [HGM] OK, so I have written it. Holdings are stored in the */
8771       /* penultimate board files, so they are automaticlly stored   */
8772       /* in the game history.                                       */
8773       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8774                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8775         /* Delete from holdings, by decreasing count */
8776         /* and erasing image if necessary            */
8777         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8778         if(p < (int) BlackPawn) { /* white drop */
8779              p -= (int)WhitePawn;
8780                  p = PieceToNumber((ChessSquare)p);
8781              if(p >= gameInfo.holdingsSize) p = 0;
8782              if(--board[p][BOARD_WIDTH-2] <= 0)
8783                   board[p][BOARD_WIDTH-1] = EmptySquare;
8784              if((int)board[p][BOARD_WIDTH-2] < 0)
8785                         board[p][BOARD_WIDTH-2] = 0;
8786         } else {                  /* black drop */
8787              p -= (int)BlackPawn;
8788                  p = PieceToNumber((ChessSquare)p);
8789              if(p >= gameInfo.holdingsSize) p = 0;
8790              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8791                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8792              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8793                         board[BOARD_HEIGHT-1-p][1] = 0;
8794         }
8795       }
8796       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8797           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8798         /* [HGM] holdings: Add to holdings, if holdings exist */
8799         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8800                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8801                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8802         }
8803         p = (int) captured;
8804         if (p >= (int) BlackPawn) {
8805           p -= (int)BlackPawn;
8806           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8807                   /* in Shogi restore piece to its original  first */
8808                   captured = (ChessSquare) (DEMOTED captured);
8809                   p = DEMOTED p;
8810           }
8811           p = PieceToNumber((ChessSquare)p);
8812           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8813           board[p][BOARD_WIDTH-2]++;
8814           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8815         } else {
8816           p -= (int)WhitePawn;
8817           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8818                   captured = (ChessSquare) (DEMOTED captured);
8819                   p = DEMOTED p;
8820           }
8821           p = PieceToNumber((ChessSquare)p);
8822           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8823           board[BOARD_HEIGHT-1-p][1]++;
8824           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8825         }
8826       }
8827     } else if (gameInfo.variant == VariantAtomic) {
8828       if (captured != EmptySquare) {
8829         int y, x;
8830         for (y = toY-1; y <= toY+1; y++) {
8831           for (x = toX-1; x <= toX+1; x++) {
8832             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8833                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8834               board[y][x] = EmptySquare;
8835             }
8836           }
8837         }
8838         board[toY][toX] = EmptySquare;
8839       }
8840     }
8841     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8842         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8843     } else
8844     if(promoChar == '+') {
8845         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8846         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8847     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8848         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8849     }
8850     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8851                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8852         // [HGM] superchess: take promotion piece out of holdings
8853         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8854         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8855             if(!--board[k][BOARD_WIDTH-2])
8856                 board[k][BOARD_WIDTH-1] = EmptySquare;
8857         } else {
8858             if(!--board[BOARD_HEIGHT-1-k][1])
8859                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8860         }
8861     }
8862
8863 }
8864
8865 /* Updates forwardMostMove */
8866 void
8867 MakeMove(fromX, fromY, toX, toY, promoChar)
8868      int fromX, fromY, toX, toY;
8869      int promoChar;
8870 {
8871 //    forwardMostMove++; // [HGM] bare: moved downstream
8872
8873     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8874         int timeLeft; static int lastLoadFlag=0; int king, piece;
8875         piece = boards[forwardMostMove][fromY][fromX];
8876         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8877         if(gameInfo.variant == VariantKnightmate)
8878             king += (int) WhiteUnicorn - (int) WhiteKing;
8879         if(forwardMostMove == 0) {
8880             if(blackPlaysFirst)
8881                 fprintf(serverMoves, "%s;", second.tidy);
8882             fprintf(serverMoves, "%s;", first.tidy);
8883             if(!blackPlaysFirst)
8884                 fprintf(serverMoves, "%s;", second.tidy);
8885         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8886         lastLoadFlag = loadFlag;
8887         // print base move
8888         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8889         // print castling suffix
8890         if( toY == fromY && piece == king ) {
8891             if(toX-fromX > 1)
8892                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8893             if(fromX-toX >1)
8894                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8895         }
8896         // e.p. suffix
8897         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8898              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8899              boards[forwardMostMove][toY][toX] == EmptySquare
8900              && fromX != toX && fromY != toY)
8901                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8902         // promotion suffix
8903         if(promoChar != NULLCHAR)
8904                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8905         if(!loadFlag) {
8906             fprintf(serverMoves, "/%d/%d",
8907                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8908             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8909             else                      timeLeft = blackTimeRemaining/1000;
8910             fprintf(serverMoves, "/%d", timeLeft);
8911         }
8912         fflush(serverMoves);
8913     }
8914
8915     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8916       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8917                         0, 1);
8918       return;
8919     }
8920     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8921     if (commentList[forwardMostMove+1] != NULL) {
8922         free(commentList[forwardMostMove+1]);
8923         commentList[forwardMostMove+1] = NULL;
8924     }
8925     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8926     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8927     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8928     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8929     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8930     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8931     gameInfo.result = GameUnfinished;
8932     if (gameInfo.resultDetails != NULL) {
8933         free(gameInfo.resultDetails);
8934         gameInfo.resultDetails = NULL;
8935     }
8936     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8937                               moveList[forwardMostMove - 1]);
8938     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8939                              PosFlags(forwardMostMove - 1),
8940                              fromY, fromX, toY, toX, promoChar,
8941                              parseList[forwardMostMove - 1]);
8942     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8943       case MT_NONE:
8944       case MT_STALEMATE:
8945       default:
8946         break;
8947       case MT_CHECK:
8948         if(gameInfo.variant != VariantShogi)
8949             strcat(parseList[forwardMostMove - 1], "+");
8950         break;
8951       case MT_CHECKMATE:
8952       case MT_STAINMATE:
8953         strcat(parseList[forwardMostMove - 1], "#");
8954         break;
8955     }
8956     if (appData.debugMode) {
8957         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8958     }
8959
8960 }
8961
8962 /* Updates currentMove if not pausing */
8963 void
8964 ShowMove(fromX, fromY, toX, toY)
8965 {
8966     int instant = (gameMode == PlayFromGameFile) ?
8967         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8968     if(appData.noGUI) return;
8969     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8970         if (!instant) {
8971             if (forwardMostMove == currentMove + 1) {
8972                 AnimateMove(boards[forwardMostMove - 1],
8973                             fromX, fromY, toX, toY);
8974             }
8975             if (appData.highlightLastMove) {
8976                 SetHighlights(fromX, fromY, toX, toY);
8977             }
8978         }
8979         currentMove = forwardMostMove;
8980     }
8981
8982     if (instant) return;
8983
8984     DisplayMove(currentMove - 1);
8985     DrawPosition(FALSE, boards[currentMove]);
8986     DisplayBothClocks();
8987     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8988 }
8989
8990 void SendEgtPath(ChessProgramState *cps)
8991 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8992         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8993
8994         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8995
8996         while(*p) {
8997             char c, *q = name+1, *r, *s;
8998
8999             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9000             while(*p && *p != ',') *q++ = *p++;
9001             *q++ = ':'; *q = 0;
9002             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9003                 strcmp(name, ",nalimov:") == 0 ) {
9004                 // take nalimov path from the menu-changeable option first, if it is defined
9005               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9006                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9007             } else
9008             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9009                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9010                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9011                 s = r = StrStr(s, ":") + 1; // beginning of path info
9012                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9013                 c = *r; *r = 0;             // temporarily null-terminate path info
9014                     *--q = 0;               // strip of trailig ':' from name
9015                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9016                 *r = c;
9017                 SendToProgram(buf,cps);     // send egtbpath command for this format
9018             }
9019             if(*p == ',') p++; // read away comma to position for next format name
9020         }
9021 }
9022
9023 void
9024 InitChessProgram(cps, setup)
9025      ChessProgramState *cps;
9026      int setup; /* [HGM] needed to setup FRC opening position */
9027 {
9028     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9029     if (appData.noChessProgram) return;
9030     hintRequested = FALSE;
9031     bookRequested = FALSE;
9032
9033     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9034     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9035     if(cps->memSize) { /* [HGM] memory */
9036       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9037         SendToProgram(buf, cps);
9038     }
9039     SendEgtPath(cps); /* [HGM] EGT */
9040     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9041       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9042         SendToProgram(buf, cps);
9043     }
9044
9045     SendToProgram(cps->initString, cps);
9046     if (gameInfo.variant != VariantNormal &&
9047         gameInfo.variant != VariantLoadable
9048         /* [HGM] also send variant if board size non-standard */
9049         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9050                                             ) {
9051       char *v = VariantName(gameInfo.variant);
9052       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9053         /* [HGM] in protocol 1 we have to assume all variants valid */
9054         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9055         DisplayFatalError(buf, 0, 1);
9056         return;
9057       }
9058
9059       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9060       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9061       if( gameInfo.variant == VariantXiangqi )
9062            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9063       if( gameInfo.variant == VariantShogi )
9064            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9065       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9066            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9067       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9068           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9069            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9070       if( gameInfo.variant == VariantCourier )
9071            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9072       if( gameInfo.variant == VariantSuper )
9073            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9074       if( gameInfo.variant == VariantGreat )
9075            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9076       if( gameInfo.variant == VariantSChess )
9077            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9078
9079       if(overruled) {
9080         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9081                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9082            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9083            if(StrStr(cps->variants, b) == NULL) {
9084                // specific sized variant not known, check if general sizing allowed
9085                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9086                    if(StrStr(cps->variants, "boardsize") == NULL) {
9087                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9088                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9089                        DisplayFatalError(buf, 0, 1);
9090                        return;
9091                    }
9092                    /* [HGM] here we really should compare with the maximum supported board size */
9093                }
9094            }
9095       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9096       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9097       SendToProgram(buf, cps);
9098     }
9099     currentlyInitializedVariant = gameInfo.variant;
9100
9101     /* [HGM] send opening position in FRC to first engine */
9102     if(setup) {
9103           SendToProgram("force\n", cps);
9104           SendBoard(cps, 0);
9105           /* engine is now in force mode! Set flag to wake it up after first move. */
9106           setboardSpoiledMachineBlack = 1;
9107     }
9108
9109     if (cps->sendICS) {
9110       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9111       SendToProgram(buf, cps);
9112     }
9113     cps->maybeThinking = FALSE;
9114     cps->offeredDraw = 0;
9115     if (!appData.icsActive) {
9116         SendTimeControl(cps, movesPerSession, timeControl,
9117                         timeIncrement, appData.searchDepth,
9118                         searchTime);
9119     }
9120     if (appData.showThinking
9121         // [HGM] thinking: four options require thinking output to be sent
9122         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9123                                 ) {
9124         SendToProgram("post\n", cps);
9125     }
9126     SendToProgram("hard\n", cps);
9127     if (!appData.ponderNextMove) {
9128         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9129            it without being sure what state we are in first.  "hard"
9130            is not a toggle, so that one is OK.
9131          */
9132         SendToProgram("easy\n", cps);
9133     }
9134     if (cps->usePing) {
9135       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9136       SendToProgram(buf, cps);
9137     }
9138     cps->initDone = TRUE;
9139 }
9140
9141
9142 void
9143 StartChessProgram(cps)
9144      ChessProgramState *cps;
9145 {
9146     char buf[MSG_SIZ];
9147     int err;
9148
9149     if (appData.noChessProgram) return;
9150     cps->initDone = FALSE;
9151
9152     if (strcmp(cps->host, "localhost") == 0) {
9153         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9154     } else if (*appData.remoteShell == NULLCHAR) {
9155         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9156     } else {
9157         if (*appData.remoteUser == NULLCHAR) {
9158           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9159                     cps->program);
9160         } else {
9161           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9162                     cps->host, appData.remoteUser, cps->program);
9163         }
9164         err = StartChildProcess(buf, "", &cps->pr);
9165     }
9166
9167     if (err != 0) {
9168       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9169         DisplayFatalError(buf, err, 1);
9170         cps->pr = NoProc;
9171         cps->isr = NULL;
9172         return;
9173     }
9174
9175     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9176     if (cps->protocolVersion > 1) {
9177       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9178       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9179       cps->comboCnt = 0;  //                and values of combo boxes
9180       SendToProgram(buf, cps);
9181     } else {
9182       SendToProgram("xboard\n", cps);
9183     }
9184 }
9185
9186
9187 void
9188 TwoMachinesEventIfReady P((void))
9189 {
9190   if (first.lastPing != first.lastPong) {
9191     DisplayMessage("", _("Waiting for first chess program"));
9192     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9193     return;
9194   }
9195   if (second.lastPing != second.lastPong) {
9196     DisplayMessage("", _("Waiting for second chess program"));
9197     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9198     return;
9199   }
9200   ThawUI();
9201   TwoMachinesEvent();
9202 }
9203
9204 void
9205 NextMatchGame P((void))
9206 {
9207     int index; /* [HGM] autoinc: step load index during match */
9208     Reset(FALSE, TRUE);
9209     if (*appData.loadGameFile != NULLCHAR) {
9210         index = appData.loadGameIndex;
9211         if(index < 0) { // [HGM] autoinc
9212             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9213             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9214         }
9215         LoadGameFromFile(appData.loadGameFile,
9216                          index,
9217                          appData.loadGameFile, FALSE);
9218     } else if (*appData.loadPositionFile != NULLCHAR) {
9219         index = appData.loadPositionIndex;
9220         if(index < 0) { // [HGM] autoinc
9221             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9222             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9223         }
9224         LoadPositionFromFile(appData.loadPositionFile,
9225                              index,
9226                              appData.loadPositionFile);
9227     }
9228     TwoMachinesEventIfReady();
9229 }
9230
9231 void UserAdjudicationEvent( int result )
9232 {
9233     ChessMove gameResult = GameIsDrawn;
9234
9235     if( result > 0 ) {
9236         gameResult = WhiteWins;
9237     }
9238     else if( result < 0 ) {
9239         gameResult = BlackWins;
9240     }
9241
9242     if( gameMode == TwoMachinesPlay ) {
9243         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9244     }
9245 }
9246
9247
9248 // [HGM] save: calculate checksum of game to make games easily identifiable
9249 int StringCheckSum(char *s)
9250 {
9251         int i = 0;
9252         if(s==NULL) return 0;
9253         while(*s) i = i*259 + *s++;
9254         return i;
9255 }
9256
9257 int GameCheckSum()
9258 {
9259         int i, sum=0;
9260         for(i=backwardMostMove; i<forwardMostMove; i++) {
9261                 sum += pvInfoList[i].depth;
9262                 sum += StringCheckSum(parseList[i]);
9263                 sum += StringCheckSum(commentList[i]);
9264                 sum *= 261;
9265         }
9266         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9267         return sum + StringCheckSum(commentList[i]);
9268 } // end of save patch
9269
9270 void
9271 GameEnds(result, resultDetails, whosays)
9272      ChessMove result;
9273      char *resultDetails;
9274      int whosays;
9275 {
9276     GameMode nextGameMode;
9277     int isIcsGame;
9278     char buf[MSG_SIZ], popupRequested = 0;
9279
9280     if(endingGame) return; /* [HGM] crash: forbid recursion */
9281     endingGame = 1;
9282     if(twoBoards) { // [HGM] dual: switch back to one board
9283         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9284         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9285     }
9286     if (appData.debugMode) {
9287       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9288               result, resultDetails ? resultDetails : "(null)", whosays);
9289     }
9290
9291     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9292
9293     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9294         /* If we are playing on ICS, the server decides when the
9295            game is over, but the engine can offer to draw, claim
9296            a draw, or resign.
9297          */
9298 #if ZIPPY
9299         if (appData.zippyPlay && first.initDone) {
9300             if (result == GameIsDrawn) {
9301                 /* In case draw still needs to be claimed */
9302                 SendToICS(ics_prefix);
9303                 SendToICS("draw\n");
9304             } else if (StrCaseStr(resultDetails, "resign")) {
9305                 SendToICS(ics_prefix);
9306                 SendToICS("resign\n");
9307             }
9308         }
9309 #endif
9310         endingGame = 0; /* [HGM] crash */
9311         return;
9312     }
9313
9314     /* If we're loading the game from a file, stop */
9315     if (whosays == GE_FILE) {
9316       (void) StopLoadGameTimer();
9317       gameFileFP = NULL;
9318     }
9319
9320     /* Cancel draw offers */
9321     first.offeredDraw = second.offeredDraw = 0;
9322
9323     /* If this is an ICS game, only ICS can really say it's done;
9324        if not, anyone can. */
9325     isIcsGame = (gameMode == IcsPlayingWhite ||
9326                  gameMode == IcsPlayingBlack ||
9327                  gameMode == IcsObserving    ||
9328                  gameMode == IcsExamining);
9329
9330     if (!isIcsGame || whosays == GE_ICS) {
9331         /* OK -- not an ICS game, or ICS said it was done */
9332         StopClocks();
9333         if (!isIcsGame && !appData.noChessProgram)
9334           SetUserThinkingEnables();
9335
9336         /* [HGM] if a machine claims the game end we verify this claim */
9337         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9338             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9339                 char claimer;
9340                 ChessMove trueResult = (ChessMove) -1;
9341
9342                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9343                                             first.twoMachinesColor[0] :
9344                                             second.twoMachinesColor[0] ;
9345
9346                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9347                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9348                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9349                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9350                 } else
9351                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9352                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9353                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9354                 } else
9355                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9356                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9357                 }
9358
9359                 // now verify win claims, but not in drop games, as we don't understand those yet
9360                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9361                                                  || gameInfo.variant == VariantGreat) &&
9362                     (result == WhiteWins && claimer == 'w' ||
9363                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9364                       if (appData.debugMode) {
9365                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9366                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9367                       }
9368                       if(result != trueResult) {
9369                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9370                               result = claimer == 'w' ? BlackWins : WhiteWins;
9371                               resultDetails = buf;
9372                       }
9373                 } else
9374                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9375                     && (forwardMostMove <= backwardMostMove ||
9376                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9377                         (claimer=='b')==(forwardMostMove&1))
9378                                                                                   ) {
9379                       /* [HGM] verify: draws that were not flagged are false claims */
9380                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9381                       result = claimer == 'w' ? BlackWins : WhiteWins;
9382                       resultDetails = buf;
9383                 }
9384                 /* (Claiming a loss is accepted no questions asked!) */
9385             }
9386             /* [HGM] bare: don't allow bare King to win */
9387             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9388                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9389                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9390                && result != GameIsDrawn)
9391             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9392                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9393                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9394                         if(p >= 0 && p <= (int)WhiteKing) k++;
9395                 }
9396                 if (appData.debugMode) {
9397                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9398                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9399                 }
9400                 if(k <= 1) {
9401                         result = GameIsDrawn;
9402                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9403                         resultDetails = buf;
9404                 }
9405             }
9406         }
9407
9408
9409         if(serverMoves != NULL && !loadFlag) { char c = '=';
9410             if(result==WhiteWins) c = '+';
9411             if(result==BlackWins) c = '-';
9412             if(resultDetails != NULL)
9413                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9414         }
9415         if (resultDetails != NULL) {
9416             gameInfo.result = result;
9417             gameInfo.resultDetails = StrSave(resultDetails);
9418
9419             /* display last move only if game was not loaded from file */
9420             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9421                 DisplayMove(currentMove - 1);
9422
9423             if (forwardMostMove != 0) {
9424                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9425                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9426                                                                 ) {
9427                     if (*appData.saveGameFile != NULLCHAR) {
9428                         SaveGameToFile(appData.saveGameFile, TRUE);
9429                     } else if (appData.autoSaveGames) {
9430                         AutoSaveGame();
9431                     }
9432                     if (*appData.savePositionFile != NULLCHAR) {
9433                         SavePositionToFile(appData.savePositionFile);
9434                     }
9435                 }
9436             }
9437
9438             /* Tell program how game ended in case it is learning */
9439             /* [HGM] Moved this to after saving the PGN, just in case */
9440             /* engine died and we got here through time loss. In that */
9441             /* case we will get a fatal error writing the pipe, which */
9442             /* would otherwise lose us the PGN.                       */
9443             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9444             /* output during GameEnds should never be fatal anymore   */
9445             if (gameMode == MachinePlaysWhite ||
9446                 gameMode == MachinePlaysBlack ||
9447                 gameMode == TwoMachinesPlay ||
9448                 gameMode == IcsPlayingWhite ||
9449                 gameMode == IcsPlayingBlack ||
9450                 gameMode == BeginningOfGame) {
9451                 char buf[MSG_SIZ];
9452                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9453                         resultDetails);
9454                 if (first.pr != NoProc) {
9455                     SendToProgram(buf, &first);
9456                 }
9457                 if (second.pr != NoProc &&
9458                     gameMode == TwoMachinesPlay) {
9459                     SendToProgram(buf, &second);
9460                 }
9461             }
9462         }
9463
9464         if (appData.icsActive) {
9465             if (appData.quietPlay &&
9466                 (gameMode == IcsPlayingWhite ||
9467                  gameMode == IcsPlayingBlack)) {
9468                 SendToICS(ics_prefix);
9469                 SendToICS("set shout 1\n");
9470             }
9471             nextGameMode = IcsIdle;
9472             ics_user_moved = FALSE;
9473             /* clean up premove.  It's ugly when the game has ended and the
9474              * premove highlights are still on the board.
9475              */
9476             if (gotPremove) {
9477               gotPremove = FALSE;
9478               ClearPremoveHighlights();
9479               DrawPosition(FALSE, boards[currentMove]);
9480             }
9481             if (whosays == GE_ICS) {
9482                 switch (result) {
9483                 case WhiteWins:
9484                     if (gameMode == IcsPlayingWhite)
9485                         PlayIcsWinSound();
9486                     else if(gameMode == IcsPlayingBlack)
9487                         PlayIcsLossSound();
9488                     break;
9489                 case BlackWins:
9490                     if (gameMode == IcsPlayingBlack)
9491                         PlayIcsWinSound();
9492                     else if(gameMode == IcsPlayingWhite)
9493                         PlayIcsLossSound();
9494                     break;
9495                 case GameIsDrawn:
9496                     PlayIcsDrawSound();
9497                     break;
9498                 default:
9499                     PlayIcsUnfinishedSound();
9500                 }
9501             }
9502         } else if (gameMode == EditGame ||
9503                    gameMode == PlayFromGameFile ||
9504                    gameMode == AnalyzeMode ||
9505                    gameMode == AnalyzeFile) {
9506             nextGameMode = gameMode;
9507         } else {
9508             nextGameMode = EndOfGame;
9509         }
9510         pausing = FALSE;
9511         ModeHighlight();
9512     } else {
9513         nextGameMode = gameMode;
9514     }
9515
9516     if (appData.noChessProgram) {
9517         gameMode = nextGameMode;
9518         ModeHighlight();
9519         endingGame = 0; /* [HGM] crash */
9520         return;
9521     }
9522
9523     if (first.reuse) {
9524         /* Put first chess program into idle state */
9525         if (first.pr != NoProc &&
9526             (gameMode == MachinePlaysWhite ||
9527              gameMode == MachinePlaysBlack ||
9528              gameMode == TwoMachinesPlay ||
9529              gameMode == IcsPlayingWhite ||
9530              gameMode == IcsPlayingBlack ||
9531              gameMode == BeginningOfGame)) {
9532             SendToProgram("force\n", &first);
9533             if (first.usePing) {
9534               char buf[MSG_SIZ];
9535               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9536               SendToProgram(buf, &first);
9537             }
9538         }
9539     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9540         /* Kill off first chess program */
9541         if (first.isr != NULL)
9542           RemoveInputSource(first.isr);
9543         first.isr = NULL;
9544
9545         if (first.pr != NoProc) {
9546             ExitAnalyzeMode();
9547             DoSleep( appData.delayBeforeQuit );
9548             SendToProgram("quit\n", &first);
9549             DoSleep( appData.delayAfterQuit );
9550             DestroyChildProcess(first.pr, first.useSigterm);
9551         }
9552         first.pr = NoProc;
9553     }
9554     if (second.reuse) {
9555         /* Put second chess program into idle state */
9556         if (second.pr != NoProc &&
9557             gameMode == TwoMachinesPlay) {
9558             SendToProgram("force\n", &second);
9559             if (second.usePing) {
9560               char buf[MSG_SIZ];
9561               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9562               SendToProgram(buf, &second);
9563             }
9564         }
9565     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9566         /* Kill off second chess program */
9567         if (second.isr != NULL)
9568           RemoveInputSource(second.isr);
9569         second.isr = NULL;
9570
9571         if (second.pr != NoProc) {
9572             DoSleep( appData.delayBeforeQuit );
9573             SendToProgram("quit\n", &second);
9574             DoSleep( appData.delayAfterQuit );
9575             DestroyChildProcess(second.pr, second.useSigterm);
9576         }
9577         second.pr = NoProc;
9578     }
9579
9580     if (matchMode && gameMode == TwoMachinesPlay) {
9581         switch (result) {
9582         case WhiteWins:
9583           if (first.twoMachinesColor[0] == 'w') {
9584             first.matchWins++;
9585           } else {
9586             second.matchWins++;
9587           }
9588           break;
9589         case BlackWins:
9590           if (first.twoMachinesColor[0] == 'b') {
9591             first.matchWins++;
9592           } else {
9593             second.matchWins++;
9594           }
9595           break;
9596         default:
9597           break;
9598         }
9599         if (matchGame < appData.matchGames) {
9600             char *tmp;
9601             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9602                 tmp = first.twoMachinesColor;
9603                 first.twoMachinesColor = second.twoMachinesColor;
9604                 second.twoMachinesColor = tmp;
9605             }
9606             gameMode = nextGameMode;
9607             matchGame++;
9608             if(appData.matchPause>10000 || appData.matchPause<10)
9609                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9610             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9611             endingGame = 0; /* [HGM] crash */
9612             return;
9613         } else {
9614             gameMode = nextGameMode;
9615             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9616                      first.tidy, second.tidy,
9617                      first.matchWins, second.matchWins,
9618                      appData.matchGames - (first.matchWins + second.matchWins));
9619             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9620             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9621                 first.twoMachinesColor = "black\n";
9622                 second.twoMachinesColor = "white\n";
9623             } else {
9624                 first.twoMachinesColor = "white\n";
9625                 second.twoMachinesColor = "black\n";
9626             }
9627         }
9628     }
9629     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9630         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9631       ExitAnalyzeMode();
9632     gameMode = nextGameMode;
9633     ModeHighlight();
9634     endingGame = 0;  /* [HGM] crash */
9635     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9636       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9637         matchMode = FALSE; appData.matchGames = matchGame = 0;
9638         DisplayNote(buf);
9639       }
9640     }
9641 }
9642
9643 /* Assumes program was just initialized (initString sent).
9644    Leaves program in force mode. */
9645 void
9646 FeedMovesToProgram(cps, upto)
9647      ChessProgramState *cps;
9648      int upto;
9649 {
9650     int i;
9651
9652     if (appData.debugMode)
9653       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9654               startedFromSetupPosition ? "position and " : "",
9655               backwardMostMove, upto, cps->which);
9656     if(currentlyInitializedVariant != gameInfo.variant) {
9657       char buf[MSG_SIZ];
9658         // [HGM] variantswitch: make engine aware of new variant
9659         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9660                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9661         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9662         SendToProgram(buf, cps);
9663         currentlyInitializedVariant = gameInfo.variant;
9664     }
9665     SendToProgram("force\n", cps);
9666     if (startedFromSetupPosition) {
9667         SendBoard(cps, backwardMostMove);
9668     if (appData.debugMode) {
9669         fprintf(debugFP, "feedMoves\n");
9670     }
9671     }
9672     for (i = backwardMostMove; i < upto; i++) {
9673         SendMoveToProgram(i, cps);
9674     }
9675 }
9676
9677
9678 void
9679 ResurrectChessProgram()
9680 {
9681      /* The chess program may have exited.
9682         If so, restart it and feed it all the moves made so far. */
9683
9684     if (appData.noChessProgram || first.pr != NoProc) return;
9685
9686     StartChessProgram(&first);
9687     InitChessProgram(&first, FALSE);
9688     FeedMovesToProgram(&first, currentMove);
9689
9690     if (!first.sendTime) {
9691         /* can't tell gnuchess what its clock should read,
9692            so we bow to its notion. */
9693         ResetClocks();
9694         timeRemaining[0][currentMove] = whiteTimeRemaining;
9695         timeRemaining[1][currentMove] = blackTimeRemaining;
9696     }
9697
9698     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9699                 appData.icsEngineAnalyze) && first.analysisSupport) {
9700       SendToProgram("analyze\n", &first);
9701       first.analyzing = TRUE;
9702     }
9703 }
9704
9705 /*
9706  * Button procedures
9707  */
9708 void
9709 Reset(redraw, init)
9710      int redraw, init;
9711 {
9712     int i;
9713
9714     if (appData.debugMode) {
9715         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9716                 redraw, init, gameMode);
9717     }
9718     CleanupTail(); // [HGM] vari: delete any stored variations
9719     pausing = pauseExamInvalid = FALSE;
9720     startedFromSetupPosition = blackPlaysFirst = FALSE;
9721     firstMove = TRUE;
9722     whiteFlag = blackFlag = FALSE;
9723     userOfferedDraw = FALSE;
9724     hintRequested = bookRequested = FALSE;
9725     first.maybeThinking = FALSE;
9726     second.maybeThinking = FALSE;
9727     first.bookSuspend = FALSE; // [HGM] book
9728     second.bookSuspend = FALSE;
9729     thinkOutput[0] = NULLCHAR;
9730     lastHint[0] = NULLCHAR;
9731     ClearGameInfo(&gameInfo);
9732     gameInfo.variant = StringToVariant(appData.variant);
9733     ics_user_moved = ics_clock_paused = FALSE;
9734     ics_getting_history = H_FALSE;
9735     ics_gamenum = -1;
9736     white_holding[0] = black_holding[0] = NULLCHAR;
9737     ClearProgramStats();
9738     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9739
9740     ResetFrontEnd();
9741     ClearHighlights();
9742     flipView = appData.flipView;
9743     ClearPremoveHighlights();
9744     gotPremove = FALSE;
9745     alarmSounded = FALSE;
9746
9747     GameEnds(EndOfFile, NULL, GE_PLAYER);
9748     if(appData.serverMovesName != NULL) {
9749         /* [HGM] prepare to make moves file for broadcasting */
9750         clock_t t = clock();
9751         if(serverMoves != NULL) fclose(serverMoves);
9752         serverMoves = fopen(appData.serverMovesName, "r");
9753         if(serverMoves != NULL) {
9754             fclose(serverMoves);
9755             /* delay 15 sec before overwriting, so all clients can see end */
9756             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9757         }
9758         serverMoves = fopen(appData.serverMovesName, "w");
9759     }
9760
9761     ExitAnalyzeMode();
9762     gameMode = BeginningOfGame;
9763     ModeHighlight();
9764     if(appData.icsActive) gameInfo.variant = VariantNormal;
9765     currentMove = forwardMostMove = backwardMostMove = 0;
9766     InitPosition(redraw);
9767     for (i = 0; i < MAX_MOVES; i++) {
9768         if (commentList[i] != NULL) {
9769             free(commentList[i]);
9770             commentList[i] = NULL;
9771         }
9772     }
9773     ResetClocks();
9774     timeRemaining[0][0] = whiteTimeRemaining;
9775     timeRemaining[1][0] = blackTimeRemaining;
9776     if (first.pr == NULL) {
9777         StartChessProgram(&first);
9778     }
9779     if (init) {
9780             InitChessProgram(&first, startedFromSetupPosition);
9781     }
9782     DisplayTitle("");
9783     DisplayMessage("", "");
9784     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9785     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9786 }
9787
9788 void
9789 AutoPlayGameLoop()
9790 {
9791     for (;;) {
9792         if (!AutoPlayOneMove())
9793           return;
9794         if (matchMode || appData.timeDelay == 0)
9795           continue;
9796         if (appData.timeDelay < 0)
9797           return;
9798         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9799         break;
9800     }
9801 }
9802
9803
9804 int
9805 AutoPlayOneMove()
9806 {
9807     int fromX, fromY, toX, toY;
9808
9809     if (appData.debugMode) {
9810       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9811     }
9812
9813     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9814       return FALSE;
9815
9816     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9817       pvInfoList[currentMove].depth = programStats.depth;
9818       pvInfoList[currentMove].score = programStats.score;
9819       pvInfoList[currentMove].time  = 0;
9820       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9821     }
9822
9823     if (currentMove >= forwardMostMove) {
9824       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9825       gameMode = EditGame;
9826       ModeHighlight();
9827
9828       /* [AS] Clear current move marker at the end of a game */
9829       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9830
9831       return FALSE;
9832     }
9833
9834     toX = moveList[currentMove][2] - AAA;
9835     toY = moveList[currentMove][3] - ONE;
9836
9837     if (moveList[currentMove][1] == '@') {
9838         if (appData.highlightLastMove) {
9839             SetHighlights(-1, -1, toX, toY);
9840         }
9841     } else {
9842         fromX = moveList[currentMove][0] - AAA;
9843         fromY = moveList[currentMove][1] - ONE;
9844
9845         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9846
9847         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9848
9849         if (appData.highlightLastMove) {
9850             SetHighlights(fromX, fromY, toX, toY);
9851         }
9852     }
9853     DisplayMove(currentMove);
9854     SendMoveToProgram(currentMove++, &first);
9855     DisplayBothClocks();
9856     DrawPosition(FALSE, boards[currentMove]);
9857     // [HGM] PV info: always display, routine tests if empty
9858     DisplayComment(currentMove - 1, commentList[currentMove]);
9859     return TRUE;
9860 }
9861
9862
9863 int
9864 LoadGameOneMove(readAhead)
9865      ChessMove readAhead;
9866 {
9867     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9868     char promoChar = NULLCHAR;
9869     ChessMove moveType;
9870     char move[MSG_SIZ];
9871     char *p, *q;
9872
9873     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9874         gameMode != AnalyzeMode && gameMode != Training) {
9875         gameFileFP = NULL;
9876         return FALSE;
9877     }
9878
9879     yyboardindex = forwardMostMove;
9880     if (readAhead != EndOfFile) {
9881       moveType = readAhead;
9882     } else {
9883       if (gameFileFP == NULL)
9884           return FALSE;
9885       moveType = (ChessMove) Myylex();
9886     }
9887
9888     done = FALSE;
9889     switch (moveType) {
9890       case Comment:
9891         if (appData.debugMode)
9892           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9893         p = yy_text;
9894
9895         /* append the comment but don't display it */
9896         AppendComment(currentMove, p, FALSE);
9897         return TRUE;
9898
9899       case WhiteCapturesEnPassant:
9900       case BlackCapturesEnPassant:
9901       case WhitePromotion:
9902       case BlackPromotion:
9903       case WhiteNonPromotion:
9904       case BlackNonPromotion:
9905       case NormalMove:
9906       case WhiteKingSideCastle:
9907       case WhiteQueenSideCastle:
9908       case BlackKingSideCastle:
9909       case BlackQueenSideCastle:
9910       case WhiteKingSideCastleWild:
9911       case WhiteQueenSideCastleWild:
9912       case BlackKingSideCastleWild:
9913       case BlackQueenSideCastleWild:
9914       /* PUSH Fabien */
9915       case WhiteHSideCastleFR:
9916       case WhiteASideCastleFR:
9917       case BlackHSideCastleFR:
9918       case BlackASideCastleFR:
9919       /* POP Fabien */
9920         if (appData.debugMode)
9921           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9922         fromX = currentMoveString[0] - AAA;
9923         fromY = currentMoveString[1] - ONE;
9924         toX = currentMoveString[2] - AAA;
9925         toY = currentMoveString[3] - ONE;
9926         promoChar = currentMoveString[4];
9927         break;
9928
9929       case WhiteDrop:
9930       case BlackDrop:
9931         if (appData.debugMode)
9932           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9933         fromX = moveType == WhiteDrop ?
9934           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9935         (int) CharToPiece(ToLower(currentMoveString[0]));
9936         fromY = DROP_RANK;
9937         toX = currentMoveString[2] - AAA;
9938         toY = currentMoveString[3] - ONE;
9939         break;
9940
9941       case WhiteWins:
9942       case BlackWins:
9943       case GameIsDrawn:
9944       case GameUnfinished:
9945         if (appData.debugMode)
9946           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9947         p = strchr(yy_text, '{');
9948         if (p == NULL) p = strchr(yy_text, '(');
9949         if (p == NULL) {
9950             p = yy_text;
9951             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9952         } else {
9953             q = strchr(p, *p == '{' ? '}' : ')');
9954             if (q != NULL) *q = NULLCHAR;
9955             p++;
9956         }
9957         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9958         GameEnds(moveType, p, GE_FILE);
9959         done = TRUE;
9960         if (cmailMsgLoaded) {
9961             ClearHighlights();
9962             flipView = WhiteOnMove(currentMove);
9963             if (moveType == GameUnfinished) flipView = !flipView;
9964             if (appData.debugMode)
9965               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9966         }
9967         break;
9968
9969       case EndOfFile:
9970         if (appData.debugMode)
9971           fprintf(debugFP, "Parser hit end of file\n");
9972         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9973           case MT_NONE:
9974           case MT_CHECK:
9975             break;
9976           case MT_CHECKMATE:
9977           case MT_STAINMATE:
9978             if (WhiteOnMove(currentMove)) {
9979                 GameEnds(BlackWins, "Black mates", GE_FILE);
9980             } else {
9981                 GameEnds(WhiteWins, "White mates", GE_FILE);
9982             }
9983             break;
9984           case MT_STALEMATE:
9985             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9986             break;
9987         }
9988         done = TRUE;
9989         break;
9990
9991       case MoveNumberOne:
9992         if (lastLoadGameStart == GNUChessGame) {
9993             /* GNUChessGames have numbers, but they aren't move numbers */
9994             if (appData.debugMode)
9995               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9996                       yy_text, (int) moveType);
9997             return LoadGameOneMove(EndOfFile); /* tail recursion */
9998         }
9999         /* else fall thru */
10000
10001       case XBoardGame:
10002       case GNUChessGame:
10003       case PGNTag:
10004         /* Reached start of next game in file */
10005         if (appData.debugMode)
10006           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10007         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10008           case MT_NONE:
10009           case MT_CHECK:
10010             break;
10011           case MT_CHECKMATE:
10012           case MT_STAINMATE:
10013             if (WhiteOnMove(currentMove)) {
10014                 GameEnds(BlackWins, "Black mates", GE_FILE);
10015             } else {
10016                 GameEnds(WhiteWins, "White mates", GE_FILE);
10017             }
10018             break;
10019           case MT_STALEMATE:
10020             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10021             break;
10022         }
10023         done = TRUE;
10024         break;
10025
10026       case PositionDiagram:     /* should not happen; ignore */
10027       case ElapsedTime:         /* ignore */
10028       case NAG:                 /* ignore */
10029         if (appData.debugMode)
10030           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10031                   yy_text, (int) moveType);
10032         return LoadGameOneMove(EndOfFile); /* tail recursion */
10033
10034       case IllegalMove:
10035         if (appData.testLegality) {
10036             if (appData.debugMode)
10037               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10038             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10039                     (forwardMostMove / 2) + 1,
10040                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10041             DisplayError(move, 0);
10042             done = TRUE;
10043         } else {
10044             if (appData.debugMode)
10045               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10046                       yy_text, currentMoveString);
10047             fromX = currentMoveString[0] - AAA;
10048             fromY = currentMoveString[1] - ONE;
10049             toX = currentMoveString[2] - AAA;
10050             toY = currentMoveString[3] - ONE;
10051             promoChar = currentMoveString[4];
10052         }
10053         break;
10054
10055       case AmbiguousMove:
10056         if (appData.debugMode)
10057           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10058         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10059                 (forwardMostMove / 2) + 1,
10060                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10061         DisplayError(move, 0);
10062         done = TRUE;
10063         break;
10064
10065       default:
10066       case ImpossibleMove:
10067         if (appData.debugMode)
10068           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10069         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10070                 (forwardMostMove / 2) + 1,
10071                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10072         DisplayError(move, 0);
10073         done = TRUE;
10074         break;
10075     }
10076
10077     if (done) {
10078         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10079             DrawPosition(FALSE, boards[currentMove]);
10080             DisplayBothClocks();
10081             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10082               DisplayComment(currentMove - 1, commentList[currentMove]);
10083         }
10084         (void) StopLoadGameTimer();
10085         gameFileFP = NULL;
10086         cmailOldMove = forwardMostMove;
10087         return FALSE;
10088     } else {
10089         /* currentMoveString is set as a side-effect of yylex */
10090
10091         thinkOutput[0] = NULLCHAR;
10092         MakeMove(fromX, fromY, toX, toY, promoChar);
10093         currentMove = forwardMostMove;
10094         return TRUE;
10095     }
10096 }
10097
10098 /* Load the nth game from the given file */
10099 int
10100 LoadGameFromFile(filename, n, title, useList)
10101      char *filename;
10102      int n;
10103      char *title;
10104      /*Boolean*/ int useList;
10105 {
10106     FILE *f;
10107     char buf[MSG_SIZ];
10108
10109     if (strcmp(filename, "-") == 0) {
10110         f = stdin;
10111         title = "stdin";
10112     } else {
10113         f = fopen(filename, "rb");
10114         if (f == NULL) {
10115           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10116             DisplayError(buf, errno);
10117             return FALSE;
10118         }
10119     }
10120     if (fseek(f, 0, 0) == -1) {
10121         /* f is not seekable; probably a pipe */
10122         useList = FALSE;
10123     }
10124     if (useList && n == 0) {
10125         int error = GameListBuild(f);
10126         if (error) {
10127             DisplayError(_("Cannot build game list"), error);
10128         } else if (!ListEmpty(&gameList) &&
10129                    ((ListGame *) gameList.tailPred)->number > 1) {
10130             GameListPopUp(f, title);
10131             return TRUE;
10132         }
10133         GameListDestroy();
10134         n = 1;
10135     }
10136     if (n == 0) n = 1;
10137     return LoadGame(f, n, title, FALSE);
10138 }
10139
10140
10141 void
10142 MakeRegisteredMove()
10143 {
10144     int fromX, fromY, toX, toY;
10145     char promoChar;
10146     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10147         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10148           case CMAIL_MOVE:
10149           case CMAIL_DRAW:
10150             if (appData.debugMode)
10151               fprintf(debugFP, "Restoring %s for game %d\n",
10152                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10153
10154             thinkOutput[0] = NULLCHAR;
10155             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10156             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10157             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10158             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10159             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10160             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10161             MakeMove(fromX, fromY, toX, toY, promoChar);
10162             ShowMove(fromX, fromY, toX, toY);
10163
10164             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10165               case MT_NONE:
10166               case MT_CHECK:
10167                 break;
10168
10169               case MT_CHECKMATE:
10170               case MT_STAINMATE:
10171                 if (WhiteOnMove(currentMove)) {
10172                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10173                 } else {
10174                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10175                 }
10176                 break;
10177
10178               case MT_STALEMATE:
10179                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10180                 break;
10181             }
10182
10183             break;
10184
10185           case CMAIL_RESIGN:
10186             if (WhiteOnMove(currentMove)) {
10187                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10188             } else {
10189                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10190             }
10191             break;
10192
10193           case CMAIL_ACCEPT:
10194             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10195             break;
10196
10197           default:
10198             break;
10199         }
10200     }
10201
10202     return;
10203 }
10204
10205 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10206 int
10207 CmailLoadGame(f, gameNumber, title, useList)
10208      FILE *f;
10209      int gameNumber;
10210      char *title;
10211      int useList;
10212 {
10213     int retVal;
10214
10215     if (gameNumber > nCmailGames) {
10216         DisplayError(_("No more games in this message"), 0);
10217         return FALSE;
10218     }
10219     if (f == lastLoadGameFP) {
10220         int offset = gameNumber - lastLoadGameNumber;
10221         if (offset == 0) {
10222             cmailMsg[0] = NULLCHAR;
10223             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10224                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10225                 nCmailMovesRegistered--;
10226             }
10227             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10228             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10229                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10230             }
10231         } else {
10232             if (! RegisterMove()) return FALSE;
10233         }
10234     }
10235
10236     retVal = LoadGame(f, gameNumber, title, useList);
10237
10238     /* Make move registered during previous look at this game, if any */
10239     MakeRegisteredMove();
10240
10241     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10242         commentList[currentMove]
10243           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10244         DisplayComment(currentMove - 1, commentList[currentMove]);
10245     }
10246
10247     return retVal;
10248 }
10249
10250 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10251 int
10252 ReloadGame(offset)
10253      int offset;
10254 {
10255     int gameNumber = lastLoadGameNumber + offset;
10256     if (lastLoadGameFP == NULL) {
10257         DisplayError(_("No game has been loaded yet"), 0);
10258         return FALSE;
10259     }
10260     if (gameNumber <= 0) {
10261         DisplayError(_("Can't back up any further"), 0);
10262         return FALSE;
10263     }
10264     if (cmailMsgLoaded) {
10265         return CmailLoadGame(lastLoadGameFP, gameNumber,
10266                              lastLoadGameTitle, lastLoadGameUseList);
10267     } else {
10268         return LoadGame(lastLoadGameFP, gameNumber,
10269                         lastLoadGameTitle, lastLoadGameUseList);
10270     }
10271 }
10272
10273
10274
10275 /* Load the nth game from open file f */
10276 int
10277 LoadGame(f, gameNumber, title, useList)
10278      FILE *f;
10279      int gameNumber;
10280      char *title;
10281      int useList;
10282 {
10283     ChessMove cm;
10284     char buf[MSG_SIZ];
10285     int gn = gameNumber;
10286     ListGame *lg = NULL;
10287     int numPGNTags = 0;
10288     int err;
10289     GameMode oldGameMode;
10290     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10291
10292     if (appData.debugMode)
10293         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10294
10295     if (gameMode == Training )
10296         SetTrainingModeOff();
10297
10298     oldGameMode = gameMode;
10299     if (gameMode != BeginningOfGame) {
10300       Reset(FALSE, TRUE);
10301     }
10302
10303     gameFileFP = f;
10304     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10305         fclose(lastLoadGameFP);
10306     }
10307
10308     if (useList) {
10309         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10310
10311         if (lg) {
10312             fseek(f, lg->offset, 0);
10313             GameListHighlight(gameNumber);
10314             gn = 1;
10315         }
10316         else {
10317             DisplayError(_("Game number out of range"), 0);
10318             return FALSE;
10319         }
10320     } else {
10321         GameListDestroy();
10322         if (fseek(f, 0, 0) == -1) {
10323             if (f == lastLoadGameFP ?
10324                 gameNumber == lastLoadGameNumber + 1 :
10325                 gameNumber == 1) {
10326                 gn = 1;
10327             } else {
10328                 DisplayError(_("Can't seek on game file"), 0);
10329                 return FALSE;
10330             }
10331         }
10332     }
10333     lastLoadGameFP = f;
10334     lastLoadGameNumber = gameNumber;
10335     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10336     lastLoadGameUseList = useList;
10337
10338     yynewfile(f);
10339
10340     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10341       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10342                 lg->gameInfo.black);
10343             DisplayTitle(buf);
10344     } else if (*title != NULLCHAR) {
10345         if (gameNumber > 1) {
10346           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10347             DisplayTitle(buf);
10348         } else {
10349             DisplayTitle(title);
10350         }
10351     }
10352
10353     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10354         gameMode = PlayFromGameFile;
10355         ModeHighlight();
10356     }
10357
10358     currentMove = forwardMostMove = backwardMostMove = 0;
10359     CopyBoard(boards[0], initialPosition);
10360     StopClocks();
10361
10362     /*
10363      * Skip the first gn-1 games in the file.
10364      * Also skip over anything that precedes an identifiable
10365      * start of game marker, to avoid being confused by
10366      * garbage at the start of the file.  Currently
10367      * recognized start of game markers are the move number "1",
10368      * the pattern "gnuchess .* game", the pattern
10369      * "^[#;%] [^ ]* game file", and a PGN tag block.
10370      * A game that starts with one of the latter two patterns
10371      * will also have a move number 1, possibly
10372      * following a position diagram.
10373      * 5-4-02: Let's try being more lenient and allowing a game to
10374      * start with an unnumbered move.  Does that break anything?
10375      */
10376     cm = lastLoadGameStart = EndOfFile;
10377     while (gn > 0) {
10378         yyboardindex = forwardMostMove;
10379         cm = (ChessMove) Myylex();
10380         switch (cm) {
10381           case EndOfFile:
10382             if (cmailMsgLoaded) {
10383                 nCmailGames = CMAIL_MAX_GAMES - gn;
10384             } else {
10385                 Reset(TRUE, TRUE);
10386                 DisplayError(_("Game not found in file"), 0);
10387             }
10388             return FALSE;
10389
10390           case GNUChessGame:
10391           case XBoardGame:
10392             gn--;
10393             lastLoadGameStart = cm;
10394             break;
10395
10396           case MoveNumberOne:
10397             switch (lastLoadGameStart) {
10398               case GNUChessGame:
10399               case XBoardGame:
10400               case PGNTag:
10401                 break;
10402               case MoveNumberOne:
10403               case EndOfFile:
10404                 gn--;           /* count this game */
10405                 lastLoadGameStart = cm;
10406                 break;
10407               default:
10408                 /* impossible */
10409                 break;
10410             }
10411             break;
10412
10413           case PGNTag:
10414             switch (lastLoadGameStart) {
10415               case GNUChessGame:
10416               case PGNTag:
10417               case MoveNumberOne:
10418               case EndOfFile:
10419                 gn--;           /* count this game */
10420                 lastLoadGameStart = cm;
10421                 break;
10422               case XBoardGame:
10423                 lastLoadGameStart = cm; /* game counted already */
10424                 break;
10425               default:
10426                 /* impossible */
10427                 break;
10428             }
10429             if (gn > 0) {
10430                 do {
10431                     yyboardindex = forwardMostMove;
10432                     cm = (ChessMove) Myylex();
10433                 } while (cm == PGNTag || cm == Comment);
10434             }
10435             break;
10436
10437           case WhiteWins:
10438           case BlackWins:
10439           case GameIsDrawn:
10440             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10441                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10442                     != CMAIL_OLD_RESULT) {
10443                     nCmailResults ++ ;
10444                     cmailResult[  CMAIL_MAX_GAMES
10445                                 - gn - 1] = CMAIL_OLD_RESULT;
10446                 }
10447             }
10448             break;
10449
10450           case NormalMove:
10451             /* Only a NormalMove can be at the start of a game
10452              * without a position diagram. */
10453             if (lastLoadGameStart == EndOfFile ) {
10454               gn--;
10455               lastLoadGameStart = MoveNumberOne;
10456             }
10457             break;
10458
10459           default:
10460             break;
10461         }
10462     }
10463
10464     if (appData.debugMode)
10465       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10466
10467     if (cm == XBoardGame) {
10468         /* Skip any header junk before position diagram and/or move 1 */
10469         for (;;) {
10470             yyboardindex = forwardMostMove;
10471             cm = (ChessMove) Myylex();
10472
10473             if (cm == EndOfFile ||
10474                 cm == GNUChessGame || cm == XBoardGame) {
10475                 /* Empty game; pretend end-of-file and handle later */
10476                 cm = EndOfFile;
10477                 break;
10478             }
10479
10480             if (cm == MoveNumberOne || cm == PositionDiagram ||
10481                 cm == PGNTag || cm == Comment)
10482               break;
10483         }
10484     } else if (cm == GNUChessGame) {
10485         if (gameInfo.event != NULL) {
10486             free(gameInfo.event);
10487         }
10488         gameInfo.event = StrSave(yy_text);
10489     }
10490
10491     startedFromSetupPosition = FALSE;
10492     while (cm == PGNTag) {
10493         if (appData.debugMode)
10494           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10495         err = ParsePGNTag(yy_text, &gameInfo);
10496         if (!err) numPGNTags++;
10497
10498         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10499         if(gameInfo.variant != oldVariant) {
10500             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10501             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10502             InitPosition(TRUE);
10503             oldVariant = gameInfo.variant;
10504             if (appData.debugMode)
10505               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10506         }
10507
10508
10509         if (gameInfo.fen != NULL) {
10510           Board initial_position;
10511           startedFromSetupPosition = TRUE;
10512           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10513             Reset(TRUE, TRUE);
10514             DisplayError(_("Bad FEN position in file"), 0);
10515             return FALSE;
10516           }
10517           CopyBoard(boards[0], initial_position);
10518           if (blackPlaysFirst) {
10519             currentMove = forwardMostMove = backwardMostMove = 1;
10520             CopyBoard(boards[1], initial_position);
10521             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10522             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10523             timeRemaining[0][1] = whiteTimeRemaining;
10524             timeRemaining[1][1] = blackTimeRemaining;
10525             if (commentList[0] != NULL) {
10526               commentList[1] = commentList[0];
10527               commentList[0] = NULL;
10528             }
10529           } else {
10530             currentMove = forwardMostMove = backwardMostMove = 0;
10531           }
10532           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10533           {   int i;
10534               initialRulePlies = FENrulePlies;
10535               for( i=0; i< nrCastlingRights; i++ )
10536                   initialRights[i] = initial_position[CASTLING][i];
10537           }
10538           yyboardindex = forwardMostMove;
10539           free(gameInfo.fen);
10540           gameInfo.fen = NULL;
10541         }
10542
10543         yyboardindex = forwardMostMove;
10544         cm = (ChessMove) Myylex();
10545
10546         /* Handle comments interspersed among the tags */
10547         while (cm == Comment) {
10548             char *p;
10549             if (appData.debugMode)
10550               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10551             p = yy_text;
10552             AppendComment(currentMove, p, FALSE);
10553             yyboardindex = forwardMostMove;
10554             cm = (ChessMove) Myylex();
10555         }
10556     }
10557
10558     /* don't rely on existence of Event tag since if game was
10559      * pasted from clipboard the Event tag may not exist
10560      */
10561     if (numPGNTags > 0){
10562         char *tags;
10563         if (gameInfo.variant == VariantNormal) {
10564           VariantClass v = StringToVariant(gameInfo.event);
10565           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10566           if(v < VariantShogi) gameInfo.variant = v;
10567         }
10568         if (!matchMode) {
10569           if( appData.autoDisplayTags ) {
10570             tags = PGNTags(&gameInfo);
10571             TagsPopUp(tags, CmailMsg());
10572             free(tags);
10573           }
10574         }
10575     } else {
10576         /* Make something up, but don't display it now */
10577         SetGameInfo();
10578         TagsPopDown();
10579     }
10580
10581     if (cm == PositionDiagram) {
10582         int i, j;
10583         char *p;
10584         Board initial_position;
10585
10586         if (appData.debugMode)
10587           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10588
10589         if (!startedFromSetupPosition) {
10590             p = yy_text;
10591             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10592               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10593                 switch (*p) {
10594                   case '{':
10595                   case '[':
10596                   case '-':
10597                   case ' ':
10598                   case '\t':
10599                   case '\n':
10600                   case '\r':
10601                     break;
10602                   default:
10603                     initial_position[i][j++] = CharToPiece(*p);
10604                     break;
10605                 }
10606             while (*p == ' ' || *p == '\t' ||
10607                    *p == '\n' || *p == '\r') p++;
10608
10609             if (strncmp(p, "black", strlen("black"))==0)
10610               blackPlaysFirst = TRUE;
10611             else
10612               blackPlaysFirst = FALSE;
10613             startedFromSetupPosition = TRUE;
10614
10615             CopyBoard(boards[0], initial_position);
10616             if (blackPlaysFirst) {
10617                 currentMove = forwardMostMove = backwardMostMove = 1;
10618                 CopyBoard(boards[1], initial_position);
10619                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10620                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10621                 timeRemaining[0][1] = whiteTimeRemaining;
10622                 timeRemaining[1][1] = blackTimeRemaining;
10623                 if (commentList[0] != NULL) {
10624                     commentList[1] = commentList[0];
10625                     commentList[0] = NULL;
10626                 }
10627             } else {
10628                 currentMove = forwardMostMove = backwardMostMove = 0;
10629             }
10630         }
10631         yyboardindex = forwardMostMove;
10632         cm = (ChessMove) Myylex();
10633     }
10634
10635     if (first.pr == NoProc) {
10636         StartChessProgram(&first);
10637     }
10638     InitChessProgram(&first, FALSE);
10639     SendToProgram("force\n", &first);
10640     if (startedFromSetupPosition) {
10641         SendBoard(&first, forwardMostMove);
10642     if (appData.debugMode) {
10643         fprintf(debugFP, "Load Game\n");
10644     }
10645         DisplayBothClocks();
10646     }
10647
10648     /* [HGM] server: flag to write setup moves in broadcast file as one */
10649     loadFlag = appData.suppressLoadMoves;
10650
10651     while (cm == Comment) {
10652         char *p;
10653         if (appData.debugMode)
10654           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10655         p = yy_text;
10656         AppendComment(currentMove, p, FALSE);
10657         yyboardindex = forwardMostMove;
10658         cm = (ChessMove) Myylex();
10659     }
10660
10661     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10662         cm == WhiteWins || cm == BlackWins ||
10663         cm == GameIsDrawn || cm == GameUnfinished) {
10664         DisplayMessage("", _("No moves in game"));
10665         if (cmailMsgLoaded) {
10666             if (appData.debugMode)
10667               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10668             ClearHighlights();
10669             flipView = FALSE;
10670         }
10671         DrawPosition(FALSE, boards[currentMove]);
10672         DisplayBothClocks();
10673         gameMode = EditGame;
10674         ModeHighlight();
10675         gameFileFP = NULL;
10676         cmailOldMove = 0;
10677         return TRUE;
10678     }
10679
10680     // [HGM] PV info: routine tests if comment empty
10681     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10682         DisplayComment(currentMove - 1, commentList[currentMove]);
10683     }
10684     if (!matchMode && appData.timeDelay != 0)
10685       DrawPosition(FALSE, boards[currentMove]);
10686
10687     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10688       programStats.ok_to_send = 1;
10689     }
10690
10691     /* if the first token after the PGN tags is a move
10692      * and not move number 1, retrieve it from the parser
10693      */
10694     if (cm != MoveNumberOne)
10695         LoadGameOneMove(cm);
10696
10697     /* load the remaining moves from the file */
10698     while (LoadGameOneMove(EndOfFile)) {
10699       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10700       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10701     }
10702
10703     /* rewind to the start of the game */
10704     currentMove = backwardMostMove;
10705
10706     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10707
10708     if (oldGameMode == AnalyzeFile ||
10709         oldGameMode == AnalyzeMode) {
10710       AnalyzeFileEvent();
10711     }
10712
10713     if (matchMode || appData.timeDelay == 0) {
10714       ToEndEvent();
10715       gameMode = EditGame;
10716       ModeHighlight();
10717     } else if (appData.timeDelay > 0) {
10718       AutoPlayGameLoop();
10719     }
10720
10721     if (appData.debugMode)
10722         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10723
10724     loadFlag = 0; /* [HGM] true game starts */
10725     return TRUE;
10726 }
10727
10728 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10729 int
10730 ReloadPosition(offset)
10731      int offset;
10732 {
10733     int positionNumber = lastLoadPositionNumber + offset;
10734     if (lastLoadPositionFP == NULL) {
10735         DisplayError(_("No position has been loaded yet"), 0);
10736         return FALSE;
10737     }
10738     if (positionNumber <= 0) {
10739         DisplayError(_("Can't back up any further"), 0);
10740         return FALSE;
10741     }
10742     return LoadPosition(lastLoadPositionFP, positionNumber,
10743                         lastLoadPositionTitle);
10744 }
10745
10746 /* Load the nth position from the given file */
10747 int
10748 LoadPositionFromFile(filename, n, title)
10749      char *filename;
10750      int n;
10751      char *title;
10752 {
10753     FILE *f;
10754     char buf[MSG_SIZ];
10755
10756     if (strcmp(filename, "-") == 0) {
10757         return LoadPosition(stdin, n, "stdin");
10758     } else {
10759         f = fopen(filename, "rb");
10760         if (f == NULL) {
10761             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10762             DisplayError(buf, errno);
10763             return FALSE;
10764         } else {
10765             return LoadPosition(f, n, title);
10766         }
10767     }
10768 }
10769
10770 /* Load the nth position from the given open file, and close it */
10771 int
10772 LoadPosition(f, positionNumber, title)
10773      FILE *f;
10774      int positionNumber;
10775      char *title;
10776 {
10777     char *p, line[MSG_SIZ];
10778     Board initial_position;
10779     int i, j, fenMode, pn;
10780
10781     if (gameMode == Training )
10782         SetTrainingModeOff();
10783
10784     if (gameMode != BeginningOfGame) {
10785         Reset(FALSE, TRUE);
10786     }
10787     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10788         fclose(lastLoadPositionFP);
10789     }
10790     if (positionNumber == 0) positionNumber = 1;
10791     lastLoadPositionFP = f;
10792     lastLoadPositionNumber = positionNumber;
10793     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10794     if (first.pr == NoProc) {
10795       StartChessProgram(&first);
10796       InitChessProgram(&first, FALSE);
10797     }
10798     pn = positionNumber;
10799     if (positionNumber < 0) {
10800         /* Negative position number means to seek to that byte offset */
10801         if (fseek(f, -positionNumber, 0) == -1) {
10802             DisplayError(_("Can't seek on position file"), 0);
10803             return FALSE;
10804         };
10805         pn = 1;
10806     } else {
10807         if (fseek(f, 0, 0) == -1) {
10808             if (f == lastLoadPositionFP ?
10809                 positionNumber == lastLoadPositionNumber + 1 :
10810                 positionNumber == 1) {
10811                 pn = 1;
10812             } else {
10813                 DisplayError(_("Can't seek on position file"), 0);
10814                 return FALSE;
10815             }
10816         }
10817     }
10818     /* See if this file is FEN or old-style xboard */
10819     if (fgets(line, MSG_SIZ, f) == NULL) {
10820         DisplayError(_("Position not found in file"), 0);
10821         return FALSE;
10822     }
10823     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10824     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10825
10826     if (pn >= 2) {
10827         if (fenMode || line[0] == '#') pn--;
10828         while (pn > 0) {
10829             /* skip positions before number pn */
10830             if (fgets(line, MSG_SIZ, f) == NULL) {
10831                 Reset(TRUE, TRUE);
10832                 DisplayError(_("Position not found in file"), 0);
10833                 return FALSE;
10834             }
10835             if (fenMode || line[0] == '#') pn--;
10836         }
10837     }
10838
10839     if (fenMode) {
10840         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10841             DisplayError(_("Bad FEN position in file"), 0);
10842             return FALSE;
10843         }
10844     } else {
10845         (void) fgets(line, MSG_SIZ, f);
10846         (void) fgets(line, MSG_SIZ, f);
10847
10848         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10849             (void) fgets(line, MSG_SIZ, f);
10850             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10851                 if (*p == ' ')
10852                   continue;
10853                 initial_position[i][j++] = CharToPiece(*p);
10854             }
10855         }
10856
10857         blackPlaysFirst = FALSE;
10858         if (!feof(f)) {
10859             (void) fgets(line, MSG_SIZ, f);
10860             if (strncmp(line, "black", strlen("black"))==0)
10861               blackPlaysFirst = TRUE;
10862         }
10863     }
10864     startedFromSetupPosition = TRUE;
10865
10866     SendToProgram("force\n", &first);
10867     CopyBoard(boards[0], initial_position);
10868     if (blackPlaysFirst) {
10869         currentMove = forwardMostMove = backwardMostMove = 1;
10870         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10871         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10872         CopyBoard(boards[1], initial_position);
10873         DisplayMessage("", _("Black to play"));
10874     } else {
10875         currentMove = forwardMostMove = backwardMostMove = 0;
10876         DisplayMessage("", _("White to play"));
10877     }
10878     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10879     SendBoard(&first, forwardMostMove);
10880     if (appData.debugMode) {
10881 int i, j;
10882   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10883   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10884         fprintf(debugFP, "Load Position\n");
10885     }
10886
10887     if (positionNumber > 1) {
10888       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10889         DisplayTitle(line);
10890     } else {
10891         DisplayTitle(title);
10892     }
10893     gameMode = EditGame;
10894     ModeHighlight();
10895     ResetClocks();
10896     timeRemaining[0][1] = whiteTimeRemaining;
10897     timeRemaining[1][1] = blackTimeRemaining;
10898     DrawPosition(FALSE, boards[currentMove]);
10899
10900     return TRUE;
10901 }
10902
10903
10904 void
10905 CopyPlayerNameIntoFileName(dest, src)
10906      char **dest, *src;
10907 {
10908     while (*src != NULLCHAR && *src != ',') {
10909         if (*src == ' ') {
10910             *(*dest)++ = '_';
10911             src++;
10912         } else {
10913             *(*dest)++ = *src++;
10914         }
10915     }
10916 }
10917
10918 char *DefaultFileName(ext)
10919      char *ext;
10920 {
10921     static char def[MSG_SIZ];
10922     char *p;
10923
10924     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10925         p = def;
10926         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10927         *p++ = '-';
10928         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10929         *p++ = '.';
10930         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10931     } else {
10932         def[0] = NULLCHAR;
10933     }
10934     return def;
10935 }
10936
10937 /* Save the current game to the given file */
10938 int
10939 SaveGameToFile(filename, append)
10940      char *filename;
10941      int append;
10942 {
10943     FILE *f;
10944     char buf[MSG_SIZ];
10945
10946     if (strcmp(filename, "-") == 0) {
10947         return SaveGame(stdout, 0, NULL);
10948     } else {
10949         f = fopen(filename, append ? "a" : "w");
10950         if (f == NULL) {
10951             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10952             DisplayError(buf, errno);
10953             return FALSE;
10954         } else {
10955             return SaveGame(f, 0, NULL);
10956         }
10957     }
10958 }
10959
10960 char *
10961 SavePart(str)
10962      char *str;
10963 {
10964     static char buf[MSG_SIZ];
10965     char *p;
10966
10967     p = strchr(str, ' ');
10968     if (p == NULL) return str;
10969     strncpy(buf, str, p - str);
10970     buf[p - str] = NULLCHAR;
10971     return buf;
10972 }
10973
10974 #define PGN_MAX_LINE 75
10975
10976 #define PGN_SIDE_WHITE  0
10977 #define PGN_SIDE_BLACK  1
10978
10979 /* [AS] */
10980 static int FindFirstMoveOutOfBook( int side )
10981 {
10982     int result = -1;
10983
10984     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10985         int index = backwardMostMove;
10986         int has_book_hit = 0;
10987
10988         if( (index % 2) != side ) {
10989             index++;
10990         }
10991
10992         while( index < forwardMostMove ) {
10993             /* Check to see if engine is in book */
10994             int depth = pvInfoList[index].depth;
10995             int score = pvInfoList[index].score;
10996             int in_book = 0;
10997
10998             if( depth <= 2 ) {
10999                 in_book = 1;
11000             }
11001             else if( score == 0 && depth == 63 ) {
11002                 in_book = 1; /* Zappa */
11003             }
11004             else if( score == 2 && depth == 99 ) {
11005                 in_book = 1; /* Abrok */
11006             }
11007
11008             has_book_hit += in_book;
11009
11010             if( ! in_book ) {
11011                 result = index;
11012
11013                 break;
11014             }
11015
11016             index += 2;
11017         }
11018     }
11019
11020     return result;
11021 }
11022
11023 /* [AS] */
11024 void GetOutOfBookInfo( char * buf )
11025 {
11026     int oob[2];
11027     int i;
11028     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11029
11030     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11031     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11032
11033     *buf = '\0';
11034
11035     if( oob[0] >= 0 || oob[1] >= 0 ) {
11036         for( i=0; i<2; i++ ) {
11037             int idx = oob[i];
11038
11039             if( idx >= 0 ) {
11040                 if( i > 0 && oob[0] >= 0 ) {
11041                     strcat( buf, "   " );
11042                 }
11043
11044                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11045                 sprintf( buf+strlen(buf), "%s%.2f",
11046                     pvInfoList[idx].score >= 0 ? "+" : "",
11047                     pvInfoList[idx].score / 100.0 );
11048             }
11049         }
11050     }
11051 }
11052
11053 /* Save game in PGN style and close the file */
11054 int
11055 SaveGamePGN(f)
11056      FILE *f;
11057 {
11058     int i, offset, linelen, newblock;
11059     time_t tm;
11060 //    char *movetext;
11061     char numtext[32];
11062     int movelen, numlen, blank;
11063     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11064
11065     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11066
11067     tm = time((time_t *) NULL);
11068
11069     PrintPGNTags(f, &gameInfo);
11070
11071     if (backwardMostMove > 0 || startedFromSetupPosition) {
11072         char *fen = PositionToFEN(backwardMostMove, NULL);
11073         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11074         fprintf(f, "\n{--------------\n");
11075         PrintPosition(f, backwardMostMove);
11076         fprintf(f, "--------------}\n");
11077         free(fen);
11078     }
11079     else {
11080         /* [AS] Out of book annotation */
11081         if( appData.saveOutOfBookInfo ) {
11082             char buf[64];
11083
11084             GetOutOfBookInfo( buf );
11085
11086             if( buf[0] != '\0' ) {
11087                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11088             }
11089         }
11090
11091         fprintf(f, "\n");
11092     }
11093
11094     i = backwardMostMove;
11095     linelen = 0;
11096     newblock = TRUE;
11097
11098     while (i < forwardMostMove) {
11099         /* Print comments preceding this move */
11100         if (commentList[i] != NULL) {
11101             if (linelen > 0) fprintf(f, "\n");
11102             fprintf(f, "%s", commentList[i]);
11103             linelen = 0;
11104             newblock = TRUE;
11105         }
11106
11107         /* Format move number */
11108         if ((i % 2) == 0)
11109           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11110         else
11111           if (newblock)
11112             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11113           else
11114             numtext[0] = NULLCHAR;
11115
11116         numlen = strlen(numtext);
11117         newblock = FALSE;
11118
11119         /* Print move number */
11120         blank = linelen > 0 && numlen > 0;
11121         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11122             fprintf(f, "\n");
11123             linelen = 0;
11124             blank = 0;
11125         }
11126         if (blank) {
11127             fprintf(f, " ");
11128             linelen++;
11129         }
11130         fprintf(f, "%s", numtext);
11131         linelen += numlen;
11132
11133         /* Get move */
11134         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11135         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11136
11137         /* Print move */
11138         blank = linelen > 0 && movelen > 0;
11139         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11140             fprintf(f, "\n");
11141             linelen = 0;
11142             blank = 0;
11143         }
11144         if (blank) {
11145             fprintf(f, " ");
11146             linelen++;
11147         }
11148         fprintf(f, "%s", move_buffer);
11149         linelen += movelen;
11150
11151         /* [AS] Add PV info if present */
11152         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11153             /* [HGM] add time */
11154             char buf[MSG_SIZ]; int seconds;
11155
11156             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11157
11158             if( seconds <= 0)
11159               buf[0] = 0;
11160             else
11161               if( seconds < 30 )
11162                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11163               else
11164                 {
11165                   seconds = (seconds + 4)/10; // round to full seconds
11166                   if( seconds < 60 )
11167                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11168                   else
11169                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11170                 }
11171
11172             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11173                       pvInfoList[i].score >= 0 ? "+" : "",
11174                       pvInfoList[i].score / 100.0,
11175                       pvInfoList[i].depth,
11176                       buf );
11177
11178             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11179
11180             /* Print score/depth */
11181             blank = linelen > 0 && movelen > 0;
11182             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11183                 fprintf(f, "\n");
11184                 linelen = 0;
11185                 blank = 0;
11186             }
11187             if (blank) {
11188                 fprintf(f, " ");
11189                 linelen++;
11190             }
11191             fprintf(f, "%s", move_buffer);
11192             linelen += movelen;
11193         }
11194
11195         i++;
11196     }
11197
11198     /* Start a new line */
11199     if (linelen > 0) fprintf(f, "\n");
11200
11201     /* Print comments after last move */
11202     if (commentList[i] != NULL) {
11203         fprintf(f, "%s\n", commentList[i]);
11204     }
11205
11206     /* Print result */
11207     if (gameInfo.resultDetails != NULL &&
11208         gameInfo.resultDetails[0] != NULLCHAR) {
11209         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11210                 PGNResult(gameInfo.result));
11211     } else {
11212         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11213     }
11214
11215     fclose(f);
11216     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11217     return TRUE;
11218 }
11219
11220 /* Save game in old style and close the file */
11221 int
11222 SaveGameOldStyle(f)
11223      FILE *f;
11224 {
11225     int i, offset;
11226     time_t tm;
11227
11228     tm = time((time_t *) NULL);
11229
11230     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11231     PrintOpponents(f);
11232
11233     if (backwardMostMove > 0 || startedFromSetupPosition) {
11234         fprintf(f, "\n[--------------\n");
11235         PrintPosition(f, backwardMostMove);
11236         fprintf(f, "--------------]\n");
11237     } else {
11238         fprintf(f, "\n");
11239     }
11240
11241     i = backwardMostMove;
11242     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11243
11244     while (i < forwardMostMove) {
11245         if (commentList[i] != NULL) {
11246             fprintf(f, "[%s]\n", commentList[i]);
11247         }
11248
11249         if ((i % 2) == 1) {
11250             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11251             i++;
11252         } else {
11253             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11254             i++;
11255             if (commentList[i] != NULL) {
11256                 fprintf(f, "\n");
11257                 continue;
11258             }
11259             if (i >= forwardMostMove) {
11260                 fprintf(f, "\n");
11261                 break;
11262             }
11263             fprintf(f, "%s\n", parseList[i]);
11264             i++;
11265         }
11266     }
11267
11268     if (commentList[i] != NULL) {
11269         fprintf(f, "[%s]\n", commentList[i]);
11270     }
11271
11272     /* This isn't really the old style, but it's close enough */
11273     if (gameInfo.resultDetails != NULL &&
11274         gameInfo.resultDetails[0] != NULLCHAR) {
11275         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11276                 gameInfo.resultDetails);
11277     } else {
11278         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11279     }
11280
11281     fclose(f);
11282     return TRUE;
11283 }
11284
11285 /* Save the current game to open file f and close the file */
11286 int
11287 SaveGame(f, dummy, dummy2)
11288      FILE *f;
11289      int dummy;
11290      char *dummy2;
11291 {
11292     if (gameMode == EditPosition) EditPositionDone(TRUE);
11293     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11294     if (appData.oldSaveStyle)
11295       return SaveGameOldStyle(f);
11296     else
11297       return SaveGamePGN(f);
11298 }
11299
11300 /* Save the current position to the given file */
11301 int
11302 SavePositionToFile(filename)
11303      char *filename;
11304 {
11305     FILE *f;
11306     char buf[MSG_SIZ];
11307
11308     if (strcmp(filename, "-") == 0) {
11309         return SavePosition(stdout, 0, NULL);
11310     } else {
11311         f = fopen(filename, "a");
11312         if (f == NULL) {
11313             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11314             DisplayError(buf, errno);
11315             return FALSE;
11316         } else {
11317             SavePosition(f, 0, NULL);
11318             return TRUE;
11319         }
11320     }
11321 }
11322
11323 /* Save the current position to the given open file and close the file */
11324 int
11325 SavePosition(f, dummy, dummy2)
11326      FILE *f;
11327      int dummy;
11328      char *dummy2;
11329 {
11330     time_t tm;
11331     char *fen;
11332
11333     if (gameMode == EditPosition) EditPositionDone(TRUE);
11334     if (appData.oldSaveStyle) {
11335         tm = time((time_t *) NULL);
11336
11337         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11338         PrintOpponents(f);
11339         fprintf(f, "[--------------\n");
11340         PrintPosition(f, currentMove);
11341         fprintf(f, "--------------]\n");
11342     } else {
11343         fen = PositionToFEN(currentMove, NULL);
11344         fprintf(f, "%s\n", fen);
11345         free(fen);
11346     }
11347     fclose(f);
11348     return TRUE;
11349 }
11350
11351 void
11352 ReloadCmailMsgEvent(unregister)
11353      int unregister;
11354 {
11355 #if !WIN32
11356     static char *inFilename = NULL;
11357     static char *outFilename;
11358     int i;
11359     struct stat inbuf, outbuf;
11360     int status;
11361
11362     /* Any registered moves are unregistered if unregister is set, */
11363     /* i.e. invoked by the signal handler */
11364     if (unregister) {
11365         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11366             cmailMoveRegistered[i] = FALSE;
11367             if (cmailCommentList[i] != NULL) {
11368                 free(cmailCommentList[i]);
11369                 cmailCommentList[i] = NULL;
11370             }
11371         }
11372         nCmailMovesRegistered = 0;
11373     }
11374
11375     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11376         cmailResult[i] = CMAIL_NOT_RESULT;
11377     }
11378     nCmailResults = 0;
11379
11380     if (inFilename == NULL) {
11381         /* Because the filenames are static they only get malloced once  */
11382         /* and they never get freed                                      */
11383         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11384         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11385
11386         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11387         sprintf(outFilename, "%s.out", appData.cmailGameName);
11388     }
11389
11390     status = stat(outFilename, &outbuf);
11391     if (status < 0) {
11392         cmailMailedMove = FALSE;
11393     } else {
11394         status = stat(inFilename, &inbuf);
11395         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11396     }
11397
11398     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11399        counts the games, notes how each one terminated, etc.
11400
11401        It would be nice to remove this kludge and instead gather all
11402        the information while building the game list.  (And to keep it
11403        in the game list nodes instead of having a bunch of fixed-size
11404        parallel arrays.)  Note this will require getting each game's
11405        termination from the PGN tags, as the game list builder does
11406        not process the game moves.  --mann
11407        */
11408     cmailMsgLoaded = TRUE;
11409     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11410
11411     /* Load first game in the file or popup game menu */
11412     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11413
11414 #endif /* !WIN32 */
11415     return;
11416 }
11417
11418 int
11419 RegisterMove()
11420 {
11421     FILE *f;
11422     char string[MSG_SIZ];
11423
11424     if (   cmailMailedMove
11425         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11426         return TRUE;            /* Allow free viewing  */
11427     }
11428
11429     /* Unregister move to ensure that we don't leave RegisterMove        */
11430     /* with the move registered when the conditions for registering no   */
11431     /* longer hold                                                       */
11432     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11433         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11434         nCmailMovesRegistered --;
11435
11436         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11437           {
11438               free(cmailCommentList[lastLoadGameNumber - 1]);
11439               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11440           }
11441     }
11442
11443     if (cmailOldMove == -1) {
11444         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11445         return FALSE;
11446     }
11447
11448     if (currentMove > cmailOldMove + 1) {
11449         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11450         return FALSE;
11451     }
11452
11453     if (currentMove < cmailOldMove) {
11454         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11455         return FALSE;
11456     }
11457
11458     if (forwardMostMove > currentMove) {
11459         /* Silently truncate extra moves */
11460         TruncateGame();
11461     }
11462
11463     if (   (currentMove == cmailOldMove + 1)
11464         || (   (currentMove == cmailOldMove)
11465             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11466                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11467         if (gameInfo.result != GameUnfinished) {
11468             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11469         }
11470
11471         if (commentList[currentMove] != NULL) {
11472             cmailCommentList[lastLoadGameNumber - 1]
11473               = StrSave(commentList[currentMove]);
11474         }
11475         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11476
11477         if (appData.debugMode)
11478           fprintf(debugFP, "Saving %s for game %d\n",
11479                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11480
11481         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11482
11483         f = fopen(string, "w");
11484         if (appData.oldSaveStyle) {
11485             SaveGameOldStyle(f); /* also closes the file */
11486
11487             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11488             f = fopen(string, "w");
11489             SavePosition(f, 0, NULL); /* also closes the file */
11490         } else {
11491             fprintf(f, "{--------------\n");
11492             PrintPosition(f, currentMove);
11493             fprintf(f, "--------------}\n\n");
11494
11495             SaveGame(f, 0, NULL); /* also closes the file*/
11496         }
11497
11498         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11499         nCmailMovesRegistered ++;
11500     } else if (nCmailGames == 1) {
11501         DisplayError(_("You have not made a move yet"), 0);
11502         return FALSE;
11503     }
11504
11505     return TRUE;
11506 }
11507
11508 void
11509 MailMoveEvent()
11510 {
11511 #if !WIN32
11512     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11513     FILE *commandOutput;
11514     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11515     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11516     int nBuffers;
11517     int i;
11518     int archived;
11519     char *arcDir;
11520
11521     if (! cmailMsgLoaded) {
11522         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11523         return;
11524     }
11525
11526     if (nCmailGames == nCmailResults) {
11527         DisplayError(_("No unfinished games"), 0);
11528         return;
11529     }
11530
11531 #if CMAIL_PROHIBIT_REMAIL
11532     if (cmailMailedMove) {
11533       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);
11534         DisplayError(msg, 0);
11535         return;
11536     }
11537 #endif
11538
11539     if (! (cmailMailedMove || RegisterMove())) return;
11540
11541     if (   cmailMailedMove
11542         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11543       snprintf(string, MSG_SIZ, partCommandString,
11544                appData.debugMode ? " -v" : "", appData.cmailGameName);
11545         commandOutput = popen(string, "r");
11546
11547         if (commandOutput == NULL) {
11548             DisplayError(_("Failed to invoke cmail"), 0);
11549         } else {
11550             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11551                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11552             }
11553             if (nBuffers > 1) {
11554                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11555                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11556                 nBytes = MSG_SIZ - 1;
11557             } else {
11558                 (void) memcpy(msg, buffer, nBytes);
11559             }
11560             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11561
11562             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11563                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11564
11565                 archived = TRUE;
11566                 for (i = 0; i < nCmailGames; i ++) {
11567                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11568                         archived = FALSE;
11569                     }
11570                 }
11571                 if (   archived
11572                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11573                         != NULL)) {
11574                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11575                            arcDir,
11576                            appData.cmailGameName,
11577                            gameInfo.date);
11578                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11579                     cmailMsgLoaded = FALSE;
11580                 }
11581             }
11582
11583             DisplayInformation(msg);
11584             pclose(commandOutput);
11585         }
11586     } else {
11587         if ((*cmailMsg) != '\0') {
11588             DisplayInformation(cmailMsg);
11589         }
11590     }
11591
11592     return;
11593 #endif /* !WIN32 */
11594 }
11595
11596 char *
11597 CmailMsg()
11598 {
11599 #if WIN32
11600     return NULL;
11601 #else
11602     int  prependComma = 0;
11603     char number[5];
11604     char string[MSG_SIZ];       /* Space for game-list */
11605     int  i;
11606
11607     if (!cmailMsgLoaded) return "";
11608
11609     if (cmailMailedMove) {
11610       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11611     } else {
11612         /* Create a list of games left */
11613       snprintf(string, MSG_SIZ, "[");
11614         for (i = 0; i < nCmailGames; i ++) {
11615             if (! (   cmailMoveRegistered[i]
11616                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11617                 if (prependComma) {
11618                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11619                 } else {
11620                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11621                     prependComma = 1;
11622                 }
11623
11624                 strcat(string, number);
11625             }
11626         }
11627         strcat(string, "]");
11628
11629         if (nCmailMovesRegistered + nCmailResults == 0) {
11630             switch (nCmailGames) {
11631               case 1:
11632                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11633                 break;
11634
11635               case 2:
11636                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11637                 break;
11638
11639               default:
11640                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11641                          nCmailGames);
11642                 break;
11643             }
11644         } else {
11645             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11646               case 1:
11647                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11648                          string);
11649                 break;
11650
11651               case 0:
11652                 if (nCmailResults == nCmailGames) {
11653                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11654                 } else {
11655                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11656                 }
11657                 break;
11658
11659               default:
11660                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11661                          string);
11662             }
11663         }
11664     }
11665     return cmailMsg;
11666 #endif /* WIN32 */
11667 }
11668
11669 void
11670 ResetGameEvent()
11671 {
11672     if (gameMode == Training)
11673       SetTrainingModeOff();
11674
11675     Reset(TRUE, TRUE);
11676     cmailMsgLoaded = FALSE;
11677     if (appData.icsActive) {
11678       SendToICS(ics_prefix);
11679       SendToICS("refresh\n");
11680     }
11681 }
11682
11683 void
11684 ExitEvent(status)
11685      int status;
11686 {
11687     exiting++;
11688     if (exiting > 2) {
11689       /* Give up on clean exit */
11690       exit(status);
11691     }
11692     if (exiting > 1) {
11693       /* Keep trying for clean exit */
11694       return;
11695     }
11696
11697     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11698
11699     if (telnetISR != NULL) {
11700       RemoveInputSource(telnetISR);
11701     }
11702     if (icsPR != NoProc) {
11703       DestroyChildProcess(icsPR, TRUE);
11704     }
11705
11706     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11707     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11708
11709     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11710     /* make sure this other one finishes before killing it!                  */
11711     if(endingGame) { int count = 0;
11712         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11713         while(endingGame && count++ < 10) DoSleep(1);
11714         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11715     }
11716
11717     /* Kill off chess programs */
11718     if (first.pr != NoProc) {
11719         ExitAnalyzeMode();
11720
11721         DoSleep( appData.delayBeforeQuit );
11722         SendToProgram("quit\n", &first);
11723         DoSleep( appData.delayAfterQuit );
11724         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11725     }
11726     if (second.pr != NoProc) {
11727         DoSleep( appData.delayBeforeQuit );
11728         SendToProgram("quit\n", &second);
11729         DoSleep( appData.delayAfterQuit );
11730         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11731     }
11732     if (first.isr != NULL) {
11733         RemoveInputSource(first.isr);
11734     }
11735     if (second.isr != NULL) {
11736         RemoveInputSource(second.isr);
11737     }
11738
11739     ShutDownFrontEnd();
11740     exit(status);
11741 }
11742
11743 void
11744 PauseEvent()
11745 {
11746     if (appData.debugMode)
11747         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11748     if (pausing) {
11749         pausing = FALSE;
11750         ModeHighlight();
11751         if (gameMode == MachinePlaysWhite ||
11752             gameMode == MachinePlaysBlack) {
11753             StartClocks();
11754         } else {
11755             DisplayBothClocks();
11756         }
11757         if (gameMode == PlayFromGameFile) {
11758             if (appData.timeDelay >= 0)
11759                 AutoPlayGameLoop();
11760         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11761             Reset(FALSE, TRUE);
11762             SendToICS(ics_prefix);
11763             SendToICS("refresh\n");
11764         } else if (currentMove < forwardMostMove) {
11765             ForwardInner(forwardMostMove);
11766         }
11767         pauseExamInvalid = FALSE;
11768     } else {
11769         switch (gameMode) {
11770           default:
11771             return;
11772           case IcsExamining:
11773             pauseExamForwardMostMove = forwardMostMove;
11774             pauseExamInvalid = FALSE;
11775             /* fall through */
11776           case IcsObserving:
11777           case IcsPlayingWhite:
11778           case IcsPlayingBlack:
11779             pausing = TRUE;
11780             ModeHighlight();
11781             return;
11782           case PlayFromGameFile:
11783             (void) StopLoadGameTimer();
11784             pausing = TRUE;
11785             ModeHighlight();
11786             break;
11787           case BeginningOfGame:
11788             if (appData.icsActive) return;
11789             /* else fall through */
11790           case MachinePlaysWhite:
11791           case MachinePlaysBlack:
11792           case TwoMachinesPlay:
11793             if (forwardMostMove == 0)
11794               return;           /* don't pause if no one has moved */
11795             if ((gameMode == MachinePlaysWhite &&
11796                  !WhiteOnMove(forwardMostMove)) ||
11797                 (gameMode == MachinePlaysBlack &&
11798                  WhiteOnMove(forwardMostMove))) {
11799                 StopClocks();
11800             }
11801             pausing = TRUE;
11802             ModeHighlight();
11803             break;
11804         }
11805     }
11806 }
11807
11808 void
11809 EditCommentEvent()
11810 {
11811     char title[MSG_SIZ];
11812
11813     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11814       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11815     } else {
11816       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11817                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11818                parseList[currentMove - 1]);
11819     }
11820
11821     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11822 }
11823
11824
11825 void
11826 EditTagsEvent()
11827 {
11828     char *tags = PGNTags(&gameInfo);
11829     EditTagsPopUp(tags, NULL);
11830     free(tags);
11831 }
11832
11833 void
11834 AnalyzeModeEvent()
11835 {
11836     if (appData.noChessProgram || gameMode == AnalyzeMode)
11837       return;
11838
11839     if (gameMode != AnalyzeFile) {
11840         if (!appData.icsEngineAnalyze) {
11841                EditGameEvent();
11842                if (gameMode != EditGame) return;
11843         }
11844         ResurrectChessProgram();
11845         SendToProgram("analyze\n", &first);
11846         first.analyzing = TRUE;
11847         /*first.maybeThinking = TRUE;*/
11848         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11849         EngineOutputPopUp();
11850     }
11851     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11852     pausing = FALSE;
11853     ModeHighlight();
11854     SetGameInfo();
11855
11856     StartAnalysisClock();
11857     GetTimeMark(&lastNodeCountTime);
11858     lastNodeCount = 0;
11859 }
11860
11861 void
11862 AnalyzeFileEvent()
11863 {
11864     if (appData.noChessProgram || gameMode == AnalyzeFile)
11865       return;
11866
11867     if (gameMode != AnalyzeMode) {
11868         EditGameEvent();
11869         if (gameMode != EditGame) return;
11870         ResurrectChessProgram();
11871         SendToProgram("analyze\n", &first);
11872         first.analyzing = TRUE;
11873         /*first.maybeThinking = TRUE;*/
11874         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11875         EngineOutputPopUp();
11876     }
11877     gameMode = AnalyzeFile;
11878     pausing = FALSE;
11879     ModeHighlight();
11880     SetGameInfo();
11881
11882     StartAnalysisClock();
11883     GetTimeMark(&lastNodeCountTime);
11884     lastNodeCount = 0;
11885 }
11886
11887 void
11888 MachineWhiteEvent()
11889 {
11890     char buf[MSG_SIZ];
11891     char *bookHit = NULL;
11892
11893     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11894       return;
11895
11896
11897     if (gameMode == PlayFromGameFile ||
11898         gameMode == TwoMachinesPlay  ||
11899         gameMode == Training         ||
11900         gameMode == AnalyzeMode      ||
11901         gameMode == EndOfGame)
11902         EditGameEvent();
11903
11904     if (gameMode == EditPosition)
11905         EditPositionDone(TRUE);
11906
11907     if (!WhiteOnMove(currentMove)) {
11908         DisplayError(_("It is not White's turn"), 0);
11909         return;
11910     }
11911
11912     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11913       ExitAnalyzeMode();
11914
11915     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11916         gameMode == AnalyzeFile)
11917         TruncateGame();
11918
11919     ResurrectChessProgram();    /* in case it isn't running */
11920     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11921         gameMode = MachinePlaysWhite;
11922         ResetClocks();
11923     } else
11924     gameMode = MachinePlaysWhite;
11925     pausing = FALSE;
11926     ModeHighlight();
11927     SetGameInfo();
11928     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11929     DisplayTitle(buf);
11930     if (first.sendName) {
11931       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11932       SendToProgram(buf, &first);
11933     }
11934     if (first.sendTime) {
11935       if (first.useColors) {
11936         SendToProgram("black\n", &first); /*gnu kludge*/
11937       }
11938       SendTimeRemaining(&first, TRUE);
11939     }
11940     if (first.useColors) {
11941       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11942     }
11943     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11944     SetMachineThinkingEnables();
11945     first.maybeThinking = TRUE;
11946     StartClocks();
11947     firstMove = FALSE;
11948
11949     if (appData.autoFlipView && !flipView) {
11950       flipView = !flipView;
11951       DrawPosition(FALSE, NULL);
11952       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11953     }
11954
11955     if(bookHit) { // [HGM] book: simulate book reply
11956         static char bookMove[MSG_SIZ]; // a bit generous?
11957
11958         programStats.nodes = programStats.depth = programStats.time =
11959         programStats.score = programStats.got_only_move = 0;
11960         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11961
11962         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11963         strcat(bookMove, bookHit);
11964         HandleMachineMove(bookMove, &first);
11965     }
11966 }
11967
11968 void
11969 MachineBlackEvent()
11970 {
11971   char buf[MSG_SIZ];
11972   char *bookHit = NULL;
11973
11974     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11975         return;
11976
11977
11978     if (gameMode == PlayFromGameFile ||
11979         gameMode == TwoMachinesPlay  ||
11980         gameMode == Training         ||
11981         gameMode == AnalyzeMode      ||
11982         gameMode == EndOfGame)
11983         EditGameEvent();
11984
11985     if (gameMode == EditPosition)
11986         EditPositionDone(TRUE);
11987
11988     if (WhiteOnMove(currentMove)) {
11989         DisplayError(_("It is not Black's turn"), 0);
11990         return;
11991     }
11992
11993     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11994       ExitAnalyzeMode();
11995
11996     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11997         gameMode == AnalyzeFile)
11998         TruncateGame();
11999
12000     ResurrectChessProgram();    /* in case it isn't running */
12001     gameMode = MachinePlaysBlack;
12002     pausing = FALSE;
12003     ModeHighlight();
12004     SetGameInfo();
12005     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12006     DisplayTitle(buf);
12007     if (first.sendName) {
12008       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12009       SendToProgram(buf, &first);
12010     }
12011     if (first.sendTime) {
12012       if (first.useColors) {
12013         SendToProgram("white\n", &first); /*gnu kludge*/
12014       }
12015       SendTimeRemaining(&first, FALSE);
12016     }
12017     if (first.useColors) {
12018       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12019     }
12020     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12021     SetMachineThinkingEnables();
12022     first.maybeThinking = TRUE;
12023     StartClocks();
12024
12025     if (appData.autoFlipView && flipView) {
12026       flipView = !flipView;
12027       DrawPosition(FALSE, NULL);
12028       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12029     }
12030     if(bookHit) { // [HGM] book: simulate book reply
12031         static char bookMove[MSG_SIZ]; // a bit generous?
12032
12033         programStats.nodes = programStats.depth = programStats.time =
12034         programStats.score = programStats.got_only_move = 0;
12035         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12036
12037         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12038         strcat(bookMove, bookHit);
12039         HandleMachineMove(bookMove, &first);
12040     }
12041 }
12042
12043
12044 void
12045 DisplayTwoMachinesTitle()
12046 {
12047     char buf[MSG_SIZ];
12048     if (appData.matchGames > 0) {
12049         if (first.twoMachinesColor[0] == 'w') {
12050           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12051                    gameInfo.white, gameInfo.black,
12052                    first.matchWins, second.matchWins,
12053                    matchGame - 1 - (first.matchWins + second.matchWins));
12054         } else {
12055           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12056                    gameInfo.white, gameInfo.black,
12057                    second.matchWins, first.matchWins,
12058                    matchGame - 1 - (first.matchWins + second.matchWins));
12059         }
12060     } else {
12061       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12062     }
12063     DisplayTitle(buf);
12064 }
12065
12066 void
12067 SettingsMenuIfReady()
12068 {
12069   if (second.lastPing != second.lastPong) {
12070     DisplayMessage("", _("Waiting for second chess program"));
12071     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12072     return;
12073   }
12074   ThawUI();
12075   DisplayMessage("", "");
12076   SettingsPopUp(&second);
12077 }
12078
12079 int
12080 WaitForSecond(DelayedEventCallback retry)
12081 {
12082     if (second.pr == NULL) {
12083         StartChessProgram(&second);
12084         if (second.protocolVersion == 1) {
12085           retry();
12086         } else {
12087           /* kludge: allow timeout for initial "feature" command */
12088           FreezeUI();
12089           DisplayMessage("", _("Starting second chess program"));
12090           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12091         }
12092         return 1;
12093     }
12094     return 0;
12095 }
12096
12097 void
12098 TwoMachinesEvent P((void))
12099 {
12100     int i;
12101     char buf[MSG_SIZ];
12102     ChessProgramState *onmove;
12103     char *bookHit = NULL;
12104     static int stalling = 0;
12105
12106     if (appData.noChessProgram) return;
12107
12108     switch (gameMode) {
12109       case TwoMachinesPlay:
12110         return;
12111       case MachinePlaysWhite:
12112       case MachinePlaysBlack:
12113         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12114             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12115             return;
12116         }
12117         /* fall through */
12118       case BeginningOfGame:
12119       case PlayFromGameFile:
12120       case EndOfGame:
12121         EditGameEvent();
12122         if (gameMode != EditGame) return;
12123         break;
12124       case EditPosition:
12125         EditPositionDone(TRUE);
12126         break;
12127       case AnalyzeMode:
12128       case AnalyzeFile:
12129         ExitAnalyzeMode();
12130         break;
12131       case EditGame:
12132       default:
12133         break;
12134     }
12135
12136 //    forwardMostMove = currentMove;
12137     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12138     ResurrectChessProgram();    /* in case first program isn't running */
12139
12140     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12141     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12142       DisplayMessage("", _("Waiting for first chess program"));
12143       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12144       return;
12145     }
12146     if(!stalling) {
12147       InitChessProgram(&second, FALSE);
12148       SendToProgram("force\n", &second);
12149     }
12150     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12151       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12152       stalling = 1;
12153       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12154       return;
12155     }
12156     stalling = 0;
12157     DisplayMessage("", "");
12158     if (startedFromSetupPosition) {
12159         SendBoard(&second, backwardMostMove);
12160     if (appData.debugMode) {
12161         fprintf(debugFP, "Two Machines\n");
12162     }
12163     }
12164     for (i = backwardMostMove; i < forwardMostMove; i++) {
12165         SendMoveToProgram(i, &second);
12166     }
12167
12168     gameMode = TwoMachinesPlay;
12169     pausing = FALSE;
12170     ModeHighlight();
12171     SetGameInfo();
12172     DisplayTwoMachinesTitle();
12173     firstMove = TRUE;
12174     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12175         onmove = &first;
12176     } else {
12177         onmove = &second;
12178     }
12179
12180     SendToProgram(first.computerString, &first);
12181     if (first.sendName) {
12182       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12183       SendToProgram(buf, &first);
12184     }
12185     SendToProgram(second.computerString, &second);
12186     if (second.sendName) {
12187       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12188       SendToProgram(buf, &second);
12189     }
12190
12191     ResetClocks();
12192     if (!first.sendTime || !second.sendTime) {
12193         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12194         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12195     }
12196     if (onmove->sendTime) {
12197       if (onmove->useColors) {
12198         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12199       }
12200       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12201     }
12202     if (onmove->useColors) {
12203       SendToProgram(onmove->twoMachinesColor, onmove);
12204     }
12205     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12206 //    SendToProgram("go\n", onmove);
12207     onmove->maybeThinking = TRUE;
12208     SetMachineThinkingEnables();
12209
12210     StartClocks();
12211
12212     if(bookHit) { // [HGM] book: simulate book reply
12213         static char bookMove[MSG_SIZ]; // a bit generous?
12214
12215         programStats.nodes = programStats.depth = programStats.time =
12216         programStats.score = programStats.got_only_move = 0;
12217         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12218
12219         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12220         strcat(bookMove, bookHit);
12221         savedMessage = bookMove; // args for deferred call
12222         savedState = onmove;
12223         ScheduleDelayedEvent(DeferredBookMove, 1);
12224     }
12225 }
12226
12227 void
12228 TrainingEvent()
12229 {
12230     if (gameMode == Training) {
12231       SetTrainingModeOff();
12232       gameMode = PlayFromGameFile;
12233       DisplayMessage("", _("Training mode off"));
12234     } else {
12235       gameMode = Training;
12236       animateTraining = appData.animate;
12237
12238       /* make sure we are not already at the end of the game */
12239       if (currentMove < forwardMostMove) {
12240         SetTrainingModeOn();
12241         DisplayMessage("", _("Training mode on"));
12242       } else {
12243         gameMode = PlayFromGameFile;
12244         DisplayError(_("Already at end of game"), 0);
12245       }
12246     }
12247     ModeHighlight();
12248 }
12249
12250 void
12251 IcsClientEvent()
12252 {
12253     if (!appData.icsActive) return;
12254     switch (gameMode) {
12255       case IcsPlayingWhite:
12256       case IcsPlayingBlack:
12257       case IcsObserving:
12258       case IcsIdle:
12259       case BeginningOfGame:
12260       case IcsExamining:
12261         return;
12262
12263       case EditGame:
12264         break;
12265
12266       case EditPosition:
12267         EditPositionDone(TRUE);
12268         break;
12269
12270       case AnalyzeMode:
12271       case AnalyzeFile:
12272         ExitAnalyzeMode();
12273         break;
12274
12275       default:
12276         EditGameEvent();
12277         break;
12278     }
12279
12280     gameMode = IcsIdle;
12281     ModeHighlight();
12282     return;
12283 }
12284
12285
12286 void
12287 EditGameEvent()
12288 {
12289     int i;
12290
12291     switch (gameMode) {
12292       case Training:
12293         SetTrainingModeOff();
12294         break;
12295       case MachinePlaysWhite:
12296       case MachinePlaysBlack:
12297       case BeginningOfGame:
12298         SendToProgram("force\n", &first);
12299         SetUserThinkingEnables();
12300         break;
12301       case PlayFromGameFile:
12302         (void) StopLoadGameTimer();
12303         if (gameFileFP != NULL) {
12304             gameFileFP = NULL;
12305         }
12306         break;
12307       case EditPosition:
12308         EditPositionDone(TRUE);
12309         break;
12310       case AnalyzeMode:
12311       case AnalyzeFile:
12312         ExitAnalyzeMode();
12313         SendToProgram("force\n", &first);
12314         break;
12315       case TwoMachinesPlay:
12316         GameEnds(EndOfFile, NULL, GE_PLAYER);
12317         ResurrectChessProgram();
12318         SetUserThinkingEnables();
12319         break;
12320       case EndOfGame:
12321         ResurrectChessProgram();
12322         break;
12323       case IcsPlayingBlack:
12324       case IcsPlayingWhite:
12325         DisplayError(_("Warning: You are still playing a game"), 0);
12326         break;
12327       case IcsObserving:
12328         DisplayError(_("Warning: You are still observing a game"), 0);
12329         break;
12330       case IcsExamining:
12331         DisplayError(_("Warning: You are still examining a game"), 0);
12332         break;
12333       case IcsIdle:
12334         break;
12335       case EditGame:
12336       default:
12337         return;
12338     }
12339
12340     pausing = FALSE;
12341     StopClocks();
12342     first.offeredDraw = second.offeredDraw = 0;
12343
12344     if (gameMode == PlayFromGameFile) {
12345         whiteTimeRemaining = timeRemaining[0][currentMove];
12346         blackTimeRemaining = timeRemaining[1][currentMove];
12347         DisplayTitle("");
12348     }
12349
12350     if (gameMode == MachinePlaysWhite ||
12351         gameMode == MachinePlaysBlack ||
12352         gameMode == TwoMachinesPlay ||
12353         gameMode == EndOfGame) {
12354         i = forwardMostMove;
12355         while (i > currentMove) {
12356             SendToProgram("undo\n", &first);
12357             i--;
12358         }
12359         whiteTimeRemaining = timeRemaining[0][currentMove];
12360         blackTimeRemaining = timeRemaining[1][currentMove];
12361         DisplayBothClocks();
12362         if (whiteFlag || blackFlag) {
12363             whiteFlag = blackFlag = 0;
12364         }
12365         DisplayTitle("");
12366     }
12367
12368     gameMode = EditGame;
12369     ModeHighlight();
12370     SetGameInfo();
12371 }
12372
12373
12374 void
12375 EditPositionEvent()
12376 {
12377     if (gameMode == EditPosition) {
12378         EditGameEvent();
12379         return;
12380     }
12381
12382     EditGameEvent();
12383     if (gameMode != EditGame) return;
12384
12385     gameMode = EditPosition;
12386     ModeHighlight();
12387     SetGameInfo();
12388     if (currentMove > 0)
12389       CopyBoard(boards[0], boards[currentMove]);
12390
12391     blackPlaysFirst = !WhiteOnMove(currentMove);
12392     ResetClocks();
12393     currentMove = forwardMostMove = backwardMostMove = 0;
12394     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12395     DisplayMove(-1);
12396 }
12397
12398 void
12399 ExitAnalyzeMode()
12400 {
12401     /* [DM] icsEngineAnalyze - possible call from other functions */
12402     if (appData.icsEngineAnalyze) {
12403         appData.icsEngineAnalyze = FALSE;
12404
12405         DisplayMessage("",_("Close ICS engine analyze..."));
12406     }
12407     if (first.analysisSupport && first.analyzing) {
12408       SendToProgram("exit\n", &first);
12409       first.analyzing = FALSE;
12410     }
12411     thinkOutput[0] = NULLCHAR;
12412 }
12413
12414 void
12415 EditPositionDone(Boolean fakeRights)
12416 {
12417     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12418
12419     startedFromSetupPosition = TRUE;
12420     InitChessProgram(&first, FALSE);
12421     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12422       boards[0][EP_STATUS] = EP_NONE;
12423       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12424     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12425         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12426         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12427       } else boards[0][CASTLING][2] = NoRights;
12428     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12429         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12430         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12431       } else boards[0][CASTLING][5] = NoRights;
12432     }
12433     SendToProgram("force\n", &first);
12434     if (blackPlaysFirst) {
12435         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12436         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12437         currentMove = forwardMostMove = backwardMostMove = 1;
12438         CopyBoard(boards[1], boards[0]);
12439     } else {
12440         currentMove = forwardMostMove = backwardMostMove = 0;
12441     }
12442     SendBoard(&first, forwardMostMove);
12443     if (appData.debugMode) {
12444         fprintf(debugFP, "EditPosDone\n");
12445     }
12446     DisplayTitle("");
12447     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12448     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12449     gameMode = EditGame;
12450     ModeHighlight();
12451     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12452     ClearHighlights(); /* [AS] */
12453 }
12454
12455 /* Pause for `ms' milliseconds */
12456 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12457 void
12458 TimeDelay(ms)
12459      long ms;
12460 {
12461     TimeMark m1, m2;
12462
12463     GetTimeMark(&m1);
12464     do {
12465         GetTimeMark(&m2);
12466     } while (SubtractTimeMarks(&m2, &m1) < ms);
12467 }
12468
12469 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12470 void
12471 SendMultiLineToICS(buf)
12472      char *buf;
12473 {
12474     char temp[MSG_SIZ+1], *p;
12475     int len;
12476
12477     len = strlen(buf);
12478     if (len > MSG_SIZ)
12479       len = MSG_SIZ;
12480
12481     strncpy(temp, buf, len);
12482     temp[len] = 0;
12483
12484     p = temp;
12485     while (*p) {
12486         if (*p == '\n' || *p == '\r')
12487           *p = ' ';
12488         ++p;
12489     }
12490
12491     strcat(temp, "\n");
12492     SendToICS(temp);
12493     SendToPlayer(temp, strlen(temp));
12494 }
12495
12496 void
12497 SetWhiteToPlayEvent()
12498 {
12499     if (gameMode == EditPosition) {
12500         blackPlaysFirst = FALSE;
12501         DisplayBothClocks();    /* works because currentMove is 0 */
12502     } else if (gameMode == IcsExamining) {
12503         SendToICS(ics_prefix);
12504         SendToICS("tomove white\n");
12505     }
12506 }
12507
12508 void
12509 SetBlackToPlayEvent()
12510 {
12511     if (gameMode == EditPosition) {
12512         blackPlaysFirst = TRUE;
12513         currentMove = 1;        /* kludge */
12514         DisplayBothClocks();
12515         currentMove = 0;
12516     } else if (gameMode == IcsExamining) {
12517         SendToICS(ics_prefix);
12518         SendToICS("tomove black\n");
12519     }
12520 }
12521
12522 void
12523 EditPositionMenuEvent(selection, x, y)
12524      ChessSquare selection;
12525      int x, y;
12526 {
12527     char buf[MSG_SIZ];
12528     ChessSquare piece = boards[0][y][x];
12529
12530     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12531
12532     switch (selection) {
12533       case ClearBoard:
12534         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12535             SendToICS(ics_prefix);
12536             SendToICS("bsetup clear\n");
12537         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12538             SendToICS(ics_prefix);
12539             SendToICS("clearboard\n");
12540         } else {
12541             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12542                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12543                 for (y = 0; y < BOARD_HEIGHT; y++) {
12544                     if (gameMode == IcsExamining) {
12545                         if (boards[currentMove][y][x] != EmptySquare) {
12546                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12547                                     AAA + x, ONE + y);
12548                             SendToICS(buf);
12549                         }
12550                     } else {
12551                         boards[0][y][x] = p;
12552                     }
12553                 }
12554             }
12555         }
12556         if (gameMode == EditPosition) {
12557             DrawPosition(FALSE, boards[0]);
12558         }
12559         break;
12560
12561       case WhitePlay:
12562         SetWhiteToPlayEvent();
12563         break;
12564
12565       case BlackPlay:
12566         SetBlackToPlayEvent();
12567         break;
12568
12569       case EmptySquare:
12570         if (gameMode == IcsExamining) {
12571             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12572             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12573             SendToICS(buf);
12574         } else {
12575             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12576                 if(x == BOARD_LEFT-2) {
12577                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12578                     boards[0][y][1] = 0;
12579                 } else
12580                 if(x == BOARD_RGHT+1) {
12581                     if(y >= gameInfo.holdingsSize) break;
12582                     boards[0][y][BOARD_WIDTH-2] = 0;
12583                 } else break;
12584             }
12585             boards[0][y][x] = EmptySquare;
12586             DrawPosition(FALSE, boards[0]);
12587         }
12588         break;
12589
12590       case PromotePiece:
12591         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12592            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12593             selection = (ChessSquare) (PROMOTED piece);
12594         } else if(piece == EmptySquare) selection = WhiteSilver;
12595         else selection = (ChessSquare)((int)piece - 1);
12596         goto defaultlabel;
12597
12598       case DemotePiece:
12599         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12600            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12601             selection = (ChessSquare) (DEMOTED piece);
12602         } else if(piece == EmptySquare) selection = BlackSilver;
12603         else selection = (ChessSquare)((int)piece + 1);
12604         goto defaultlabel;
12605
12606       case WhiteQueen:
12607       case BlackQueen:
12608         if(gameInfo.variant == VariantShatranj ||
12609            gameInfo.variant == VariantXiangqi  ||
12610            gameInfo.variant == VariantCourier  ||
12611            gameInfo.variant == VariantMakruk     )
12612             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12613         goto defaultlabel;
12614
12615       case WhiteKing:
12616       case BlackKing:
12617         if(gameInfo.variant == VariantXiangqi)
12618             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12619         if(gameInfo.variant == VariantKnightmate)
12620             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12621       default:
12622         defaultlabel:
12623         if (gameMode == IcsExamining) {
12624             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12625             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12626                      PieceToChar(selection), AAA + x, ONE + y);
12627             SendToICS(buf);
12628         } else {
12629             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12630                 int n;
12631                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12632                     n = PieceToNumber(selection - BlackPawn);
12633                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12634                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12635                     boards[0][BOARD_HEIGHT-1-n][1]++;
12636                 } else
12637                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12638                     n = PieceToNumber(selection);
12639                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12640                     boards[0][n][BOARD_WIDTH-1] = selection;
12641                     boards[0][n][BOARD_WIDTH-2]++;
12642                 }
12643             } else
12644             boards[0][y][x] = selection;
12645             DrawPosition(TRUE, boards[0]);
12646         }
12647         break;
12648     }
12649 }
12650
12651
12652 void
12653 DropMenuEvent(selection, x, y)
12654      ChessSquare selection;
12655      int x, y;
12656 {
12657     ChessMove moveType;
12658
12659     switch (gameMode) {
12660       case IcsPlayingWhite:
12661       case MachinePlaysBlack:
12662         if (!WhiteOnMove(currentMove)) {
12663             DisplayMoveError(_("It is Black's turn"));
12664             return;
12665         }
12666         moveType = WhiteDrop;
12667         break;
12668       case IcsPlayingBlack:
12669       case MachinePlaysWhite:
12670         if (WhiteOnMove(currentMove)) {
12671             DisplayMoveError(_("It is White's turn"));
12672             return;
12673         }
12674         moveType = BlackDrop;
12675         break;
12676       case EditGame:
12677         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12678         break;
12679       default:
12680         return;
12681     }
12682
12683     if (moveType == BlackDrop && selection < BlackPawn) {
12684       selection = (ChessSquare) ((int) selection
12685                                  + (int) BlackPawn - (int) WhitePawn);
12686     }
12687     if (boards[currentMove][y][x] != EmptySquare) {
12688         DisplayMoveError(_("That square is occupied"));
12689         return;
12690     }
12691
12692     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12693 }
12694
12695 void
12696 AcceptEvent()
12697 {
12698     /* Accept a pending offer of any kind from opponent */
12699
12700     if (appData.icsActive) {
12701         SendToICS(ics_prefix);
12702         SendToICS("accept\n");
12703     } else if (cmailMsgLoaded) {
12704         if (currentMove == cmailOldMove &&
12705             commentList[cmailOldMove] != NULL &&
12706             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12707                    "Black offers a draw" : "White offers a draw")) {
12708             TruncateGame();
12709             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12710             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12711         } else {
12712             DisplayError(_("There is no pending offer on this move"), 0);
12713             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12714         }
12715     } else {
12716         /* Not used for offers from chess program */
12717     }
12718 }
12719
12720 void
12721 DeclineEvent()
12722 {
12723     /* Decline a pending offer of any kind from opponent */
12724
12725     if (appData.icsActive) {
12726         SendToICS(ics_prefix);
12727         SendToICS("decline\n");
12728     } else if (cmailMsgLoaded) {
12729         if (currentMove == cmailOldMove &&
12730             commentList[cmailOldMove] != NULL &&
12731             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12732                    "Black offers a draw" : "White offers a draw")) {
12733 #ifdef NOTDEF
12734             AppendComment(cmailOldMove, "Draw declined", TRUE);
12735             DisplayComment(cmailOldMove - 1, "Draw declined");
12736 #endif /*NOTDEF*/
12737         } else {
12738             DisplayError(_("There is no pending offer on this move"), 0);
12739         }
12740     } else {
12741         /* Not used for offers from chess program */
12742     }
12743 }
12744
12745 void
12746 RematchEvent()
12747 {
12748     /* Issue ICS rematch command */
12749     if (appData.icsActive) {
12750         SendToICS(ics_prefix);
12751         SendToICS("rematch\n");
12752     }
12753 }
12754
12755 void
12756 CallFlagEvent()
12757 {
12758     /* Call your opponent's flag (claim a win on time) */
12759     if (appData.icsActive) {
12760         SendToICS(ics_prefix);
12761         SendToICS("flag\n");
12762     } else {
12763         switch (gameMode) {
12764           default:
12765             return;
12766           case MachinePlaysWhite:
12767             if (whiteFlag) {
12768                 if (blackFlag)
12769                   GameEnds(GameIsDrawn, "Both players ran out of time",
12770                            GE_PLAYER);
12771                 else
12772                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12773             } else {
12774                 DisplayError(_("Your opponent is not out of time"), 0);
12775             }
12776             break;
12777           case MachinePlaysBlack:
12778             if (blackFlag) {
12779                 if (whiteFlag)
12780                   GameEnds(GameIsDrawn, "Both players ran out of time",
12781                            GE_PLAYER);
12782                 else
12783                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12784             } else {
12785                 DisplayError(_("Your opponent is not out of time"), 0);
12786             }
12787             break;
12788         }
12789     }
12790 }
12791
12792 void
12793 ClockClick(int which)
12794 {       // [HGM] code moved to back-end from winboard.c
12795         if(which) { // black clock
12796           if (gameMode == EditPosition || gameMode == IcsExamining) {
12797             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12798             SetBlackToPlayEvent();
12799           } else if (gameMode == EditGame || shiftKey) {
12800             AdjustClock(which, -1);
12801           } else if (gameMode == IcsPlayingWhite ||
12802                      gameMode == MachinePlaysBlack) {
12803             CallFlagEvent();
12804           }
12805         } else { // white clock
12806           if (gameMode == EditPosition || gameMode == IcsExamining) {
12807             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12808             SetWhiteToPlayEvent();
12809           } else if (gameMode == EditGame || shiftKey) {
12810             AdjustClock(which, -1);
12811           } else if (gameMode == IcsPlayingBlack ||
12812                    gameMode == MachinePlaysWhite) {
12813             CallFlagEvent();
12814           }
12815         }
12816 }
12817
12818 void
12819 DrawEvent()
12820 {
12821     /* Offer draw or accept pending draw offer from opponent */
12822
12823     if (appData.icsActive) {
12824         /* Note: tournament rules require draw offers to be
12825            made after you make your move but before you punch
12826            your clock.  Currently ICS doesn't let you do that;
12827            instead, you immediately punch your clock after making
12828            a move, but you can offer a draw at any time. */
12829
12830         SendToICS(ics_prefix);
12831         SendToICS("draw\n");
12832         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12833     } else if (cmailMsgLoaded) {
12834         if (currentMove == cmailOldMove &&
12835             commentList[cmailOldMove] != NULL &&
12836             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12837                    "Black offers a draw" : "White offers a draw")) {
12838             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12839             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12840         } else if (currentMove == cmailOldMove + 1) {
12841             char *offer = WhiteOnMove(cmailOldMove) ?
12842               "White offers a draw" : "Black offers a draw";
12843             AppendComment(currentMove, offer, TRUE);
12844             DisplayComment(currentMove - 1, offer);
12845             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12846         } else {
12847             DisplayError(_("You must make your move before offering a draw"), 0);
12848             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12849         }
12850     } else if (first.offeredDraw) {
12851         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12852     } else {
12853         if (first.sendDrawOffers) {
12854             SendToProgram("draw\n", &first);
12855             userOfferedDraw = TRUE;
12856         }
12857     }
12858 }
12859
12860 void
12861 AdjournEvent()
12862 {
12863     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12864
12865     if (appData.icsActive) {
12866         SendToICS(ics_prefix);
12867         SendToICS("adjourn\n");
12868     } else {
12869         /* Currently GNU Chess doesn't offer or accept Adjourns */
12870     }
12871 }
12872
12873
12874 void
12875 AbortEvent()
12876 {
12877     /* Offer Abort or accept pending Abort offer from opponent */
12878
12879     if (appData.icsActive) {
12880         SendToICS(ics_prefix);
12881         SendToICS("abort\n");
12882     } else {
12883         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12884     }
12885 }
12886
12887 void
12888 ResignEvent()
12889 {
12890     /* Resign.  You can do this even if it's not your turn. */
12891
12892     if (appData.icsActive) {
12893         SendToICS(ics_prefix);
12894         SendToICS("resign\n");
12895     } else {
12896         switch (gameMode) {
12897           case MachinePlaysWhite:
12898             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12899             break;
12900           case MachinePlaysBlack:
12901             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12902             break;
12903           case EditGame:
12904             if (cmailMsgLoaded) {
12905                 TruncateGame();
12906                 if (WhiteOnMove(cmailOldMove)) {
12907                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12908                 } else {
12909                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12910                 }
12911                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12912             }
12913             break;
12914           default:
12915             break;
12916         }
12917     }
12918 }
12919
12920
12921 void
12922 StopObservingEvent()
12923 {
12924     /* Stop observing current games */
12925     SendToICS(ics_prefix);
12926     SendToICS("unobserve\n");
12927 }
12928
12929 void
12930 StopExaminingEvent()
12931 {
12932     /* Stop observing current game */
12933     SendToICS(ics_prefix);
12934     SendToICS("unexamine\n");
12935 }
12936
12937 void
12938 ForwardInner(target)
12939      int target;
12940 {
12941     int limit;
12942
12943     if (appData.debugMode)
12944         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12945                 target, currentMove, forwardMostMove);
12946
12947     if (gameMode == EditPosition)
12948       return;
12949
12950     if (gameMode == PlayFromGameFile && !pausing)
12951       PauseEvent();
12952
12953     if (gameMode == IcsExamining && pausing)
12954       limit = pauseExamForwardMostMove;
12955     else
12956       limit = forwardMostMove;
12957
12958     if (target > limit) target = limit;
12959
12960     if (target > 0 && moveList[target - 1][0]) {
12961         int fromX, fromY, toX, toY;
12962         toX = moveList[target - 1][2] - AAA;
12963         toY = moveList[target - 1][3] - ONE;
12964         if (moveList[target - 1][1] == '@') {
12965             if (appData.highlightLastMove) {
12966                 SetHighlights(-1, -1, toX, toY);
12967             }
12968         } else {
12969             fromX = moveList[target - 1][0] - AAA;
12970             fromY = moveList[target - 1][1] - ONE;
12971             if (target == currentMove + 1) {
12972                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12973             }
12974             if (appData.highlightLastMove) {
12975                 SetHighlights(fromX, fromY, toX, toY);
12976             }
12977         }
12978     }
12979     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12980         gameMode == Training || gameMode == PlayFromGameFile ||
12981         gameMode == AnalyzeFile) {
12982         while (currentMove < target) {
12983             SendMoveToProgram(currentMove++, &first);
12984         }
12985     } else {
12986         currentMove = target;
12987     }
12988
12989     if (gameMode == EditGame || gameMode == EndOfGame) {
12990         whiteTimeRemaining = timeRemaining[0][currentMove];
12991         blackTimeRemaining = timeRemaining[1][currentMove];
12992     }
12993     DisplayBothClocks();
12994     DisplayMove(currentMove - 1);
12995     DrawPosition(FALSE, boards[currentMove]);
12996     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12997     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12998         DisplayComment(currentMove - 1, commentList[currentMove]);
12999     }
13000 }
13001
13002
13003 void
13004 ForwardEvent()
13005 {
13006     if (gameMode == IcsExamining && !pausing) {
13007         SendToICS(ics_prefix);
13008         SendToICS("forward\n");
13009     } else {
13010         ForwardInner(currentMove + 1);
13011     }
13012 }
13013
13014 void
13015 ToEndEvent()
13016 {
13017     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13018         /* to optimze, we temporarily turn off analysis mode while we feed
13019          * the remaining moves to the engine. Otherwise we get analysis output
13020          * after each move.
13021          */
13022         if (first.analysisSupport) {
13023           SendToProgram("exit\nforce\n", &first);
13024           first.analyzing = FALSE;
13025         }
13026     }
13027
13028     if (gameMode == IcsExamining && !pausing) {
13029         SendToICS(ics_prefix);
13030         SendToICS("forward 999999\n");
13031     } else {
13032         ForwardInner(forwardMostMove);
13033     }
13034
13035     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13036         /* we have fed all the moves, so reactivate analysis mode */
13037         SendToProgram("analyze\n", &first);
13038         first.analyzing = TRUE;
13039         /*first.maybeThinking = TRUE;*/
13040         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13041     }
13042 }
13043
13044 void
13045 BackwardInner(target)
13046      int target;
13047 {
13048     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13049
13050     if (appData.debugMode)
13051         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13052                 target, currentMove, forwardMostMove);
13053
13054     if (gameMode == EditPosition) return;
13055     if (currentMove <= backwardMostMove) {
13056         ClearHighlights();
13057         DrawPosition(full_redraw, boards[currentMove]);
13058         return;
13059     }
13060     if (gameMode == PlayFromGameFile && !pausing)
13061       PauseEvent();
13062
13063     if (moveList[target][0]) {
13064         int fromX, fromY, toX, toY;
13065         toX = moveList[target][2] - AAA;
13066         toY = moveList[target][3] - ONE;
13067         if (moveList[target][1] == '@') {
13068             if (appData.highlightLastMove) {
13069                 SetHighlights(-1, -1, toX, toY);
13070             }
13071         } else {
13072             fromX = moveList[target][0] - AAA;
13073             fromY = moveList[target][1] - ONE;
13074             if (target == currentMove - 1) {
13075                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13076             }
13077             if (appData.highlightLastMove) {
13078                 SetHighlights(fromX, fromY, toX, toY);
13079             }
13080         }
13081     }
13082     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13083         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13084         while (currentMove > target) {
13085             SendToProgram("undo\n", &first);
13086             currentMove--;
13087         }
13088     } else {
13089         currentMove = target;
13090     }
13091
13092     if (gameMode == EditGame || gameMode == EndOfGame) {
13093         whiteTimeRemaining = timeRemaining[0][currentMove];
13094         blackTimeRemaining = timeRemaining[1][currentMove];
13095     }
13096     DisplayBothClocks();
13097     DisplayMove(currentMove - 1);
13098     DrawPosition(full_redraw, boards[currentMove]);
13099     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13100     // [HGM] PV info: routine tests if comment empty
13101     DisplayComment(currentMove - 1, commentList[currentMove]);
13102 }
13103
13104 void
13105 BackwardEvent()
13106 {
13107     if (gameMode == IcsExamining && !pausing) {
13108         SendToICS(ics_prefix);
13109         SendToICS("backward\n");
13110     } else {
13111         BackwardInner(currentMove - 1);
13112     }
13113 }
13114
13115 void
13116 ToStartEvent()
13117 {
13118     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13119         /* to optimize, we temporarily turn off analysis mode while we undo
13120          * all the moves. Otherwise we get analysis output after each undo.
13121          */
13122         if (first.analysisSupport) {
13123           SendToProgram("exit\nforce\n", &first);
13124           first.analyzing = FALSE;
13125         }
13126     }
13127
13128     if (gameMode == IcsExamining && !pausing) {
13129         SendToICS(ics_prefix);
13130         SendToICS("backward 999999\n");
13131     } else {
13132         BackwardInner(backwardMostMove);
13133     }
13134
13135     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13136         /* we have fed all the moves, so reactivate analysis mode */
13137         SendToProgram("analyze\n", &first);
13138         first.analyzing = TRUE;
13139         /*first.maybeThinking = TRUE;*/
13140         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13141     }
13142 }
13143
13144 void
13145 ToNrEvent(int to)
13146 {
13147   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13148   if (to >= forwardMostMove) to = forwardMostMove;
13149   if (to <= backwardMostMove) to = backwardMostMove;
13150   if (to < currentMove) {
13151     BackwardInner(to);
13152   } else {
13153     ForwardInner(to);
13154   }
13155 }
13156
13157 void
13158 RevertEvent(Boolean annotate)
13159 {
13160     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13161         return;
13162     }
13163     if (gameMode != IcsExamining) {
13164         DisplayError(_("You are not examining a game"), 0);
13165         return;
13166     }
13167     if (pausing) {
13168         DisplayError(_("You can't revert while pausing"), 0);
13169         return;
13170     }
13171     SendToICS(ics_prefix);
13172     SendToICS("revert\n");
13173 }
13174
13175 void
13176 RetractMoveEvent()
13177 {
13178     switch (gameMode) {
13179       case MachinePlaysWhite:
13180       case MachinePlaysBlack:
13181         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13182             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13183             return;
13184         }
13185         if (forwardMostMove < 2) return;
13186         currentMove = forwardMostMove = forwardMostMove - 2;
13187         whiteTimeRemaining = timeRemaining[0][currentMove];
13188         blackTimeRemaining = timeRemaining[1][currentMove];
13189         DisplayBothClocks();
13190         DisplayMove(currentMove - 1);
13191         ClearHighlights();/*!! could figure this out*/
13192         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13193         SendToProgram("remove\n", &first);
13194         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13195         break;
13196
13197       case BeginningOfGame:
13198       default:
13199         break;
13200
13201       case IcsPlayingWhite:
13202       case IcsPlayingBlack:
13203         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13204             SendToICS(ics_prefix);
13205             SendToICS("takeback 2\n");
13206         } else {
13207             SendToICS(ics_prefix);
13208             SendToICS("takeback 1\n");
13209         }
13210         break;
13211     }
13212 }
13213
13214 void
13215 MoveNowEvent()
13216 {
13217     ChessProgramState *cps;
13218
13219     switch (gameMode) {
13220       case MachinePlaysWhite:
13221         if (!WhiteOnMove(forwardMostMove)) {
13222             DisplayError(_("It is your turn"), 0);
13223             return;
13224         }
13225         cps = &first;
13226         break;
13227       case MachinePlaysBlack:
13228         if (WhiteOnMove(forwardMostMove)) {
13229             DisplayError(_("It is your turn"), 0);
13230             return;
13231         }
13232         cps = &first;
13233         break;
13234       case TwoMachinesPlay:
13235         if (WhiteOnMove(forwardMostMove) ==
13236             (first.twoMachinesColor[0] == 'w')) {
13237             cps = &first;
13238         } else {
13239             cps = &second;
13240         }
13241         break;
13242       case BeginningOfGame:
13243       default:
13244         return;
13245     }
13246     SendToProgram("?\n", cps);
13247 }
13248
13249 void
13250 TruncateGameEvent()
13251 {
13252     EditGameEvent();
13253     if (gameMode != EditGame) return;
13254     TruncateGame();
13255 }
13256
13257 void
13258 TruncateGame()
13259 {
13260     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13261     if (forwardMostMove > currentMove) {
13262         if (gameInfo.resultDetails != NULL) {
13263             free(gameInfo.resultDetails);
13264             gameInfo.resultDetails = NULL;
13265             gameInfo.result = GameUnfinished;
13266         }
13267         forwardMostMove = currentMove;
13268         HistorySet(parseList, backwardMostMove, forwardMostMove,
13269                    currentMove-1);
13270     }
13271 }
13272
13273 void
13274 HintEvent()
13275 {
13276     if (appData.noChessProgram) return;
13277     switch (gameMode) {
13278       case MachinePlaysWhite:
13279         if (WhiteOnMove(forwardMostMove)) {
13280             DisplayError(_("Wait until your turn"), 0);
13281             return;
13282         }
13283         break;
13284       case BeginningOfGame:
13285       case MachinePlaysBlack:
13286         if (!WhiteOnMove(forwardMostMove)) {
13287             DisplayError(_("Wait until your turn"), 0);
13288             return;
13289         }
13290         break;
13291       default:
13292         DisplayError(_("No hint available"), 0);
13293         return;
13294     }
13295     SendToProgram("hint\n", &first);
13296     hintRequested = TRUE;
13297 }
13298
13299 void
13300 BookEvent()
13301 {
13302     if (appData.noChessProgram) return;
13303     switch (gameMode) {
13304       case MachinePlaysWhite:
13305         if (WhiteOnMove(forwardMostMove)) {
13306             DisplayError(_("Wait until your turn"), 0);
13307             return;
13308         }
13309         break;
13310       case BeginningOfGame:
13311       case MachinePlaysBlack:
13312         if (!WhiteOnMove(forwardMostMove)) {
13313             DisplayError(_("Wait until your turn"), 0);
13314             return;
13315         }
13316         break;
13317       case EditPosition:
13318         EditPositionDone(TRUE);
13319         break;
13320       case TwoMachinesPlay:
13321         return;
13322       default:
13323         break;
13324     }
13325     SendToProgram("bk\n", &first);
13326     bookOutput[0] = NULLCHAR;
13327     bookRequested = TRUE;
13328 }
13329
13330 void
13331 AboutGameEvent()
13332 {
13333     char *tags = PGNTags(&gameInfo);
13334     TagsPopUp(tags, CmailMsg());
13335     free(tags);
13336 }
13337
13338 /* end button procedures */
13339
13340 void
13341 PrintPosition(fp, move)
13342      FILE *fp;
13343      int move;
13344 {
13345     int i, j;
13346
13347     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13348         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13349             char c = PieceToChar(boards[move][i][j]);
13350             fputc(c == 'x' ? '.' : c, fp);
13351             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13352         }
13353     }
13354     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13355       fprintf(fp, "white to play\n");
13356     else
13357       fprintf(fp, "black to play\n");
13358 }
13359
13360 void
13361 PrintOpponents(fp)
13362      FILE *fp;
13363 {
13364     if (gameInfo.white != NULL) {
13365         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13366     } else {
13367         fprintf(fp, "\n");
13368     }
13369 }
13370
13371 /* Find last component of program's own name, using some heuristics */
13372 void
13373 TidyProgramName(prog, host, buf)
13374      char *prog, *host, buf[MSG_SIZ];
13375 {
13376     char *p, *q;
13377     int local = (strcmp(host, "localhost") == 0);
13378     while (!local && (p = strchr(prog, ';')) != NULL) {
13379         p++;
13380         while (*p == ' ') p++;
13381         prog = p;
13382     }
13383     if (*prog == '"' || *prog == '\'') {
13384         q = strchr(prog + 1, *prog);
13385     } else {
13386         q = strchr(prog, ' ');
13387     }
13388     if (q == NULL) q = prog + strlen(prog);
13389     p = q;
13390     while (p >= prog && *p != '/' && *p != '\\') p--;
13391     p++;
13392     if(p == prog && *p == '"') p++;
13393     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13394     memcpy(buf, p, q - p);
13395     buf[q - p] = NULLCHAR;
13396     if (!local) {
13397         strcat(buf, "@");
13398         strcat(buf, host);
13399     }
13400 }
13401
13402 char *
13403 TimeControlTagValue()
13404 {
13405     char buf[MSG_SIZ];
13406     if (!appData.clockMode) {
13407       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13408     } else if (movesPerSession > 0) {
13409       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13410     } else if (timeIncrement == 0) {
13411       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13412     } else {
13413       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13414     }
13415     return StrSave(buf);
13416 }
13417
13418 void
13419 SetGameInfo()
13420 {
13421     /* This routine is used only for certain modes */
13422     VariantClass v = gameInfo.variant;
13423     ChessMove r = GameUnfinished;
13424     char *p = NULL;
13425
13426     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13427         r = gameInfo.result;
13428         p = gameInfo.resultDetails;
13429         gameInfo.resultDetails = NULL;
13430     }
13431     ClearGameInfo(&gameInfo);
13432     gameInfo.variant = v;
13433
13434     switch (gameMode) {
13435       case MachinePlaysWhite:
13436         gameInfo.event = StrSave( appData.pgnEventHeader );
13437         gameInfo.site = StrSave(HostName());
13438         gameInfo.date = PGNDate();
13439         gameInfo.round = StrSave("-");
13440         gameInfo.white = StrSave(first.tidy);
13441         gameInfo.black = StrSave(UserName());
13442         gameInfo.timeControl = TimeControlTagValue();
13443         break;
13444
13445       case MachinePlaysBlack:
13446         gameInfo.event = StrSave( appData.pgnEventHeader );
13447         gameInfo.site = StrSave(HostName());
13448         gameInfo.date = PGNDate();
13449         gameInfo.round = StrSave("-");
13450         gameInfo.white = StrSave(UserName());
13451         gameInfo.black = StrSave(first.tidy);
13452         gameInfo.timeControl = TimeControlTagValue();
13453         break;
13454
13455       case TwoMachinesPlay:
13456         gameInfo.event = StrSave( appData.pgnEventHeader );
13457         gameInfo.site = StrSave(HostName());
13458         gameInfo.date = PGNDate();
13459         if (matchGame > 0) {
13460             char buf[MSG_SIZ];
13461             snprintf(buf, MSG_SIZ, "%d", matchGame);
13462             gameInfo.round = StrSave(buf);
13463         } else {
13464             gameInfo.round = StrSave("-");
13465         }
13466         if (first.twoMachinesColor[0] == 'w') {
13467             gameInfo.white = StrSave(first.tidy);
13468             gameInfo.black = StrSave(second.tidy);
13469         } else {
13470             gameInfo.white = StrSave(second.tidy);
13471             gameInfo.black = StrSave(first.tidy);
13472         }
13473         gameInfo.timeControl = TimeControlTagValue();
13474         break;
13475
13476       case EditGame:
13477         gameInfo.event = StrSave("Edited game");
13478         gameInfo.site = StrSave(HostName());
13479         gameInfo.date = PGNDate();
13480         gameInfo.round = StrSave("-");
13481         gameInfo.white = StrSave("-");
13482         gameInfo.black = StrSave("-");
13483         gameInfo.result = r;
13484         gameInfo.resultDetails = p;
13485         break;
13486
13487       case EditPosition:
13488         gameInfo.event = StrSave("Edited position");
13489         gameInfo.site = StrSave(HostName());
13490         gameInfo.date = PGNDate();
13491         gameInfo.round = StrSave("-");
13492         gameInfo.white = StrSave("-");
13493         gameInfo.black = StrSave("-");
13494         break;
13495
13496       case IcsPlayingWhite:
13497       case IcsPlayingBlack:
13498       case IcsObserving:
13499       case IcsExamining:
13500         break;
13501
13502       case PlayFromGameFile:
13503         gameInfo.event = StrSave("Game from non-PGN file");
13504         gameInfo.site = StrSave(HostName());
13505         gameInfo.date = PGNDate();
13506         gameInfo.round = StrSave("-");
13507         gameInfo.white = StrSave("?");
13508         gameInfo.black = StrSave("?");
13509         break;
13510
13511       default:
13512         break;
13513     }
13514 }
13515
13516 void
13517 ReplaceComment(index, text)
13518      int index;
13519      char *text;
13520 {
13521     int len;
13522     char *p;
13523     float score;
13524
13525     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13526        pvInfoList[index-1].depth == len &&
13527        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13528        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13529     while (*text == '\n') text++;
13530     len = strlen(text);
13531     while (len > 0 && text[len - 1] == '\n') len--;
13532
13533     if (commentList[index] != NULL)
13534       free(commentList[index]);
13535
13536     if (len == 0) {
13537         commentList[index] = NULL;
13538         return;
13539     }
13540   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13541       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13542       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13543     commentList[index] = (char *) malloc(len + 2);
13544     strncpy(commentList[index], text, len);
13545     commentList[index][len] = '\n';
13546     commentList[index][len + 1] = NULLCHAR;
13547   } else {
13548     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13549     char *p;
13550     commentList[index] = (char *) malloc(len + 7);
13551     safeStrCpy(commentList[index], "{\n", 3);
13552     safeStrCpy(commentList[index]+2, text, len+1);
13553     commentList[index][len+2] = NULLCHAR;
13554     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13555     strcat(commentList[index], "\n}\n");
13556   }
13557 }
13558
13559 void
13560 CrushCRs(text)
13561      char *text;
13562 {
13563   char *p = text;
13564   char *q = text;
13565   char ch;
13566
13567   do {
13568     ch = *p++;
13569     if (ch == '\r') continue;
13570     *q++ = ch;
13571   } while (ch != '\0');
13572 }
13573
13574 void
13575 AppendComment(index, text, addBraces)
13576      int index;
13577      char *text;
13578      Boolean addBraces; // [HGM] braces: tells if we should add {}
13579 {
13580     int oldlen, len;
13581     char *old;
13582
13583 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13584     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13585
13586     CrushCRs(text);
13587     while (*text == '\n') text++;
13588     len = strlen(text);
13589     while (len > 0 && text[len - 1] == '\n') len--;
13590
13591     if (len == 0) return;
13592
13593     if (commentList[index] != NULL) {
13594         old = commentList[index];
13595         oldlen = strlen(old);
13596         while(commentList[index][oldlen-1] ==  '\n')
13597           commentList[index][--oldlen] = NULLCHAR;
13598         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13599         safeStrCpy(commentList[index], old, oldlen + len + 6);
13600         free(old);
13601         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13602         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13603           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13604           while (*text == '\n') { text++; len--; }
13605           commentList[index][--oldlen] = NULLCHAR;
13606       }
13607         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13608         else          strcat(commentList[index], "\n");
13609         strcat(commentList[index], text);
13610         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13611         else          strcat(commentList[index], "\n");
13612     } else {
13613         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13614         if(addBraces)
13615           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13616         else commentList[index][0] = NULLCHAR;
13617         strcat(commentList[index], text);
13618         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13619         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13620     }
13621 }
13622
13623 static char * FindStr( char * text, char * sub_text )
13624 {
13625     char * result = strstr( text, sub_text );
13626
13627     if( result != NULL ) {
13628         result += strlen( sub_text );
13629     }
13630
13631     return result;
13632 }
13633
13634 /* [AS] Try to extract PV info from PGN comment */
13635 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13636 char *GetInfoFromComment( int index, char * text )
13637 {
13638     char * sep = text, *p;
13639
13640     if( text != NULL && index > 0 ) {
13641         int score = 0;
13642         int depth = 0;
13643         int time = -1, sec = 0, deci;
13644         char * s_eval = FindStr( text, "[%eval " );
13645         char * s_emt = FindStr( text, "[%emt " );
13646
13647         if( s_eval != NULL || s_emt != NULL ) {
13648             /* New style */
13649             char delim;
13650
13651             if( s_eval != NULL ) {
13652                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13653                     return text;
13654                 }
13655
13656                 if( delim != ']' ) {
13657                     return text;
13658                 }
13659             }
13660
13661             if( s_emt != NULL ) {
13662             }
13663                 return text;
13664         }
13665         else {
13666             /* We expect something like: [+|-]nnn.nn/dd */
13667             int score_lo = 0;
13668
13669             if(*text != '{') return text; // [HGM] braces: must be normal comment
13670
13671             sep = strchr( text, '/' );
13672             if( sep == NULL || sep < (text+4) ) {
13673                 return text;
13674             }
13675
13676             p = text;
13677             if(p[1] == '(') { // comment starts with PV
13678                p = strchr(p, ')'); // locate end of PV
13679                if(p == NULL || sep < p+5) return text;
13680                // at this point we have something like "{(.*) +0.23/6 ..."
13681                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13682                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13683                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13684             }
13685             time = -1; sec = -1; deci = -1;
13686             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13687                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13688                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13689                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13690                 return text;
13691             }
13692
13693             if( score_lo < 0 || score_lo >= 100 ) {
13694                 return text;
13695             }
13696
13697             if(sec >= 0) time = 600*time + 10*sec; else
13698             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13699
13700             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13701
13702             /* [HGM] PV time: now locate end of PV info */
13703             while( *++sep >= '0' && *sep <= '9'); // strip depth
13704             if(time >= 0)
13705             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13706             if(sec >= 0)
13707             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13708             if(deci >= 0)
13709             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13710             while(*sep == ' ') sep++;
13711         }
13712
13713         if( depth <= 0 ) {
13714             return text;
13715         }
13716
13717         if( time < 0 ) {
13718             time = -1;
13719         }
13720
13721         pvInfoList[index-1].depth = depth;
13722         pvInfoList[index-1].score = score;
13723         pvInfoList[index-1].time  = 10*time; // centi-sec
13724         if(*sep == '}') *sep = 0; else *--sep = '{';
13725         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13726     }
13727     return sep;
13728 }
13729
13730 void
13731 SendToProgram(message, cps)
13732      char *message;
13733      ChessProgramState *cps;
13734 {
13735     int count, outCount, error;
13736     char buf[MSG_SIZ];
13737
13738     if (cps->pr == NULL) return;
13739     Attention(cps);
13740
13741     if (appData.debugMode) {
13742         TimeMark now;
13743         GetTimeMark(&now);
13744         fprintf(debugFP, "%ld >%-6s: %s",
13745                 SubtractTimeMarks(&now, &programStartTime),
13746                 cps->which, message);
13747     }
13748
13749     count = strlen(message);
13750     outCount = OutputToProcess(cps->pr, message, count, &error);
13751     if (outCount < count && !exiting
13752                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13753       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13754         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13755             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13756                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13757                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13758             } else {
13759                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13760             }
13761             gameInfo.resultDetails = StrSave(buf);
13762         }
13763         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13764     }
13765 }
13766
13767 void
13768 ReceiveFromProgram(isr, closure, message, count, error)
13769      InputSourceRef isr;
13770      VOIDSTAR closure;
13771      char *message;
13772      int count;
13773      int error;
13774 {
13775     char *end_str;
13776     char buf[MSG_SIZ];
13777     ChessProgramState *cps = (ChessProgramState *)closure;
13778
13779     if (isr != cps->isr) return; /* Killed intentionally */
13780     if (count <= 0) {
13781         if (count == 0) {
13782             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13783                     _(cps->which), cps->program);
13784         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13785                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13786                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13787                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13788                 } else {
13789                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13790                 }
13791                 gameInfo.resultDetails = StrSave(buf);
13792             }
13793             RemoveInputSource(cps->isr);
13794             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13795         } else {
13796             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13797                     _(cps->which), cps->program);
13798             RemoveInputSource(cps->isr);
13799
13800             /* [AS] Program is misbehaving badly... kill it */
13801             if( count == -2 ) {
13802                 DestroyChildProcess( cps->pr, 9 );
13803                 cps->pr = NoProc;
13804             }
13805
13806             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13807         }
13808         return;
13809     }
13810
13811     if ((end_str = strchr(message, '\r')) != NULL)
13812       *end_str = NULLCHAR;
13813     if ((end_str = strchr(message, '\n')) != NULL)
13814       *end_str = NULLCHAR;
13815
13816     if (appData.debugMode) {
13817         TimeMark now; int print = 1;
13818         char *quote = ""; char c; int i;
13819
13820         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13821                 char start = message[0];
13822                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13823                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13824                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13825                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13826                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13827                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13828                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13829                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13830                    sscanf(message, "hint: %c", &c)!=1 && 
13831                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13832                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13833                     print = (appData.engineComments >= 2);
13834                 }
13835                 message[0] = start; // restore original message
13836         }
13837         if(print) {
13838                 GetTimeMark(&now);
13839                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13840                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13841                         quote,
13842                         message);
13843         }
13844     }
13845
13846     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13847     if (appData.icsEngineAnalyze) {
13848         if (strstr(message, "whisper") != NULL ||
13849              strstr(message, "kibitz") != NULL ||
13850             strstr(message, "tellics") != NULL) return;
13851     }
13852
13853     HandleMachineMove(message, cps);
13854 }
13855
13856
13857 void
13858 SendTimeControl(cps, mps, tc, inc, sd, st)
13859      ChessProgramState *cps;
13860      int mps, inc, sd, st;
13861      long tc;
13862 {
13863     char buf[MSG_SIZ];
13864     int seconds;
13865
13866     if( timeControl_2 > 0 ) {
13867         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13868             tc = timeControl_2;
13869         }
13870     }
13871     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13872     inc /= cps->timeOdds;
13873     st  /= cps->timeOdds;
13874
13875     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13876
13877     if (st > 0) {
13878       /* Set exact time per move, normally using st command */
13879       if (cps->stKludge) {
13880         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13881         seconds = st % 60;
13882         if (seconds == 0) {
13883           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13884         } else {
13885           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13886         }
13887       } else {
13888         snprintf(buf, MSG_SIZ, "st %d\n", st);
13889       }
13890     } else {
13891       /* Set conventional or incremental time control, using level command */
13892       if (seconds == 0) {
13893         /* Note old gnuchess bug -- minutes:seconds used to not work.
13894            Fixed in later versions, but still avoid :seconds
13895            when seconds is 0. */
13896         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13897       } else {
13898         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13899                  seconds, inc/1000.);
13900       }
13901     }
13902     SendToProgram(buf, cps);
13903
13904     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13905     /* Orthogonally, limit search to given depth */
13906     if (sd > 0) {
13907       if (cps->sdKludge) {
13908         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13909       } else {
13910         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13911       }
13912       SendToProgram(buf, cps);
13913     }
13914
13915     if(cps->nps >= 0) { /* [HGM] nps */
13916         if(cps->supportsNPS == FALSE)
13917           cps->nps = -1; // don't use if engine explicitly says not supported!
13918         else {
13919           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13920           SendToProgram(buf, cps);
13921         }
13922     }
13923 }
13924
13925 ChessProgramState *WhitePlayer()
13926 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13927 {
13928     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13929        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13930         return &second;
13931     return &first;
13932 }
13933
13934 void
13935 SendTimeRemaining(cps, machineWhite)
13936      ChessProgramState *cps;
13937      int /*boolean*/ machineWhite;
13938 {
13939     char message[MSG_SIZ];
13940     long time, otime;
13941
13942     /* Note: this routine must be called when the clocks are stopped
13943        or when they have *just* been set or switched; otherwise
13944        it will be off by the time since the current tick started.
13945     */
13946     if (machineWhite) {
13947         time = whiteTimeRemaining / 10;
13948         otime = blackTimeRemaining / 10;
13949     } else {
13950         time = blackTimeRemaining / 10;
13951         otime = whiteTimeRemaining / 10;
13952     }
13953     /* [HGM] translate opponent's time by time-odds factor */
13954     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13955     if (appData.debugMode) {
13956         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13957     }
13958
13959     if (time <= 0) time = 1;
13960     if (otime <= 0) otime = 1;
13961
13962     snprintf(message, MSG_SIZ, "time %ld\n", time);
13963     SendToProgram(message, cps);
13964
13965     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13966     SendToProgram(message, cps);
13967 }
13968
13969 int
13970 BoolFeature(p, name, loc, cps)
13971      char **p;
13972      char *name;
13973      int *loc;
13974      ChessProgramState *cps;
13975 {
13976   char buf[MSG_SIZ];
13977   int len = strlen(name);
13978   int val;
13979
13980   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13981     (*p) += len + 1;
13982     sscanf(*p, "%d", &val);
13983     *loc = (val != 0);
13984     while (**p && **p != ' ')
13985       (*p)++;
13986     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13987     SendToProgram(buf, cps);
13988     return TRUE;
13989   }
13990   return FALSE;
13991 }
13992
13993 int
13994 IntFeature(p, name, loc, cps)
13995      char **p;
13996      char *name;
13997      int *loc;
13998      ChessProgramState *cps;
13999 {
14000   char buf[MSG_SIZ];
14001   int len = strlen(name);
14002   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14003     (*p) += len + 1;
14004     sscanf(*p, "%d", loc);
14005     while (**p && **p != ' ') (*p)++;
14006     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14007     SendToProgram(buf, cps);
14008     return TRUE;
14009   }
14010   return FALSE;
14011 }
14012
14013 int
14014 StringFeature(p, name, loc, cps)
14015      char **p;
14016      char *name;
14017      char loc[];
14018      ChessProgramState *cps;
14019 {
14020   char buf[MSG_SIZ];
14021   int len = strlen(name);
14022   if (strncmp((*p), name, len) == 0
14023       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14024     (*p) += len + 2;
14025     sscanf(*p, "%[^\"]", loc);
14026     while (**p && **p != '\"') (*p)++;
14027     if (**p == '\"') (*p)++;
14028     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14029     SendToProgram(buf, cps);
14030     return TRUE;
14031   }
14032   return FALSE;
14033 }
14034
14035 int
14036 ParseOption(Option *opt, ChessProgramState *cps)
14037 // [HGM] options: process the string that defines an engine option, and determine
14038 // name, type, default value, and allowed value range
14039 {
14040         char *p, *q, buf[MSG_SIZ];
14041         int n, min = (-1)<<31, max = 1<<31, def;
14042
14043         if(p = strstr(opt->name, " -spin ")) {
14044             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14045             if(max < min) max = min; // enforce consistency
14046             if(def < min) def = min;
14047             if(def > max) def = max;
14048             opt->value = def;
14049             opt->min = min;
14050             opt->max = max;
14051             opt->type = Spin;
14052         } else if((p = strstr(opt->name, " -slider "))) {
14053             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14054             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14055             if(max < min) max = min; // enforce consistency
14056             if(def < min) def = min;
14057             if(def > max) def = max;
14058             opt->value = def;
14059             opt->min = min;
14060             opt->max = max;
14061             opt->type = Spin; // Slider;
14062         } else if((p = strstr(opt->name, " -string "))) {
14063             opt->textValue = p+9;
14064             opt->type = TextBox;
14065         } else if((p = strstr(opt->name, " -file "))) {
14066             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14067             opt->textValue = p+7;
14068             opt->type = FileName; // FileName;
14069         } else if((p = strstr(opt->name, " -path "))) {
14070             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14071             opt->textValue = p+7;
14072             opt->type = PathName; // PathName;
14073         } else if(p = strstr(opt->name, " -check ")) {
14074             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14075             opt->value = (def != 0);
14076             opt->type = CheckBox;
14077         } else if(p = strstr(opt->name, " -combo ")) {
14078             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14079             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14080             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14081             opt->value = n = 0;
14082             while(q = StrStr(q, " /// ")) {
14083                 n++; *q = 0;    // count choices, and null-terminate each of them
14084                 q += 5;
14085                 if(*q == '*') { // remember default, which is marked with * prefix
14086                     q++;
14087                     opt->value = n;
14088                 }
14089                 cps->comboList[cps->comboCnt++] = q;
14090             }
14091             cps->comboList[cps->comboCnt++] = NULL;
14092             opt->max = n + 1;
14093             opt->type = ComboBox;
14094         } else if(p = strstr(opt->name, " -button")) {
14095             opt->type = Button;
14096         } else if(p = strstr(opt->name, " -save")) {
14097             opt->type = SaveButton;
14098         } else return FALSE;
14099         *p = 0; // terminate option name
14100         // now look if the command-line options define a setting for this engine option.
14101         if(cps->optionSettings && cps->optionSettings[0])
14102             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14103         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14104           snprintf(buf, MSG_SIZ, "option %s", p);
14105                 if(p = strstr(buf, ",")) *p = 0;
14106                 if(q = strchr(buf, '=')) switch(opt->type) {
14107                     case ComboBox:
14108                         for(n=0; n<opt->max; n++)
14109                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14110                         break;
14111                     case TextBox:
14112                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14113                         break;
14114                     case Spin:
14115                     case CheckBox:
14116                         opt->value = atoi(q+1);
14117                     default:
14118                         break;
14119                 }
14120                 strcat(buf, "\n");
14121                 SendToProgram(buf, cps);
14122         }
14123         return TRUE;
14124 }
14125
14126 void
14127 FeatureDone(cps, val)
14128      ChessProgramState* cps;
14129      int val;
14130 {
14131   DelayedEventCallback cb = GetDelayedEvent();
14132   if ((cb == InitBackEnd3 && cps == &first) ||
14133       (cb == SettingsMenuIfReady && cps == &second) ||
14134       (cb == TwoMachinesEventIfReady && cps == &second)) {
14135     CancelDelayedEvent();
14136     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14137   }
14138   cps->initDone = val;
14139 }
14140
14141 /* Parse feature command from engine */
14142 void
14143 ParseFeatures(args, cps)
14144      char* args;
14145      ChessProgramState *cps;
14146 {
14147   char *p = args;
14148   char *q;
14149   int val;
14150   char buf[MSG_SIZ];
14151
14152   for (;;) {
14153     while (*p == ' ') p++;
14154     if (*p == NULLCHAR) return;
14155
14156     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14157     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14158     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14159     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14160     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14161     if (BoolFeature(&p, "reuse", &val, cps)) {
14162       /* Engine can disable reuse, but can't enable it if user said no */
14163       if (!val) cps->reuse = FALSE;
14164       continue;
14165     }
14166     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14167     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14168       if (gameMode == TwoMachinesPlay) {
14169         DisplayTwoMachinesTitle();
14170       } else {
14171         DisplayTitle("");
14172       }
14173       continue;
14174     }
14175     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14176     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14177     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14178     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14179     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14180     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14181     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14182     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14183     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14184     if (IntFeature(&p, "done", &val, cps)) {
14185       FeatureDone(cps, val);
14186       continue;
14187     }
14188     /* Added by Tord: */
14189     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14190     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14191     /* End of additions by Tord */
14192
14193     /* [HGM] added features: */
14194     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14195     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14196     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14197     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14198     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14199     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14200     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14201         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14202           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14203             SendToProgram(buf, cps);
14204             continue;
14205         }
14206         if(cps->nrOptions >= MAX_OPTIONS) {
14207             cps->nrOptions--;
14208             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14209             DisplayError(buf, 0);
14210         }
14211         continue;
14212     }
14213     /* End of additions by HGM */
14214
14215     /* unknown feature: complain and skip */
14216     q = p;
14217     while (*q && *q != '=') q++;
14218     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14219     SendToProgram(buf, cps);
14220     p = q;
14221     if (*p == '=') {
14222       p++;
14223       if (*p == '\"') {
14224         p++;
14225         while (*p && *p != '\"') p++;
14226         if (*p == '\"') p++;
14227       } else {
14228         while (*p && *p != ' ') p++;
14229       }
14230     }
14231   }
14232
14233 }
14234
14235 void
14236 PeriodicUpdatesEvent(newState)
14237      int newState;
14238 {
14239     if (newState == appData.periodicUpdates)
14240       return;
14241
14242     appData.periodicUpdates=newState;
14243
14244     /* Display type changes, so update it now */
14245 //    DisplayAnalysis();
14246
14247     /* Get the ball rolling again... */
14248     if (newState) {
14249         AnalysisPeriodicEvent(1);
14250         StartAnalysisClock();
14251     }
14252 }
14253
14254 void
14255 PonderNextMoveEvent(newState)
14256      int newState;
14257 {
14258     if (newState == appData.ponderNextMove) return;
14259     if (gameMode == EditPosition) EditPositionDone(TRUE);
14260     if (newState) {
14261         SendToProgram("hard\n", &first);
14262         if (gameMode == TwoMachinesPlay) {
14263             SendToProgram("hard\n", &second);
14264         }
14265     } else {
14266         SendToProgram("easy\n", &first);
14267         thinkOutput[0] = NULLCHAR;
14268         if (gameMode == TwoMachinesPlay) {
14269             SendToProgram("easy\n", &second);
14270         }
14271     }
14272     appData.ponderNextMove = newState;
14273 }
14274
14275 void
14276 NewSettingEvent(option, feature, command, value)
14277      char *command;
14278      int option, value, *feature;
14279 {
14280     char buf[MSG_SIZ];
14281
14282     if (gameMode == EditPosition) EditPositionDone(TRUE);
14283     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14284     if(feature == NULL || *feature) SendToProgram(buf, &first);
14285     if (gameMode == TwoMachinesPlay) {
14286         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14287     }
14288 }
14289
14290 void
14291 ShowThinkingEvent()
14292 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14293 {
14294     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14295     int newState = appData.showThinking
14296         // [HGM] thinking: other features now need thinking output as well
14297         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14298
14299     if (oldState == newState) return;
14300     oldState = newState;
14301     if (gameMode == EditPosition) EditPositionDone(TRUE);
14302     if (oldState) {
14303         SendToProgram("post\n", &first);
14304         if (gameMode == TwoMachinesPlay) {
14305             SendToProgram("post\n", &second);
14306         }
14307     } else {
14308         SendToProgram("nopost\n", &first);
14309         thinkOutput[0] = NULLCHAR;
14310         if (gameMode == TwoMachinesPlay) {
14311             SendToProgram("nopost\n", &second);
14312         }
14313     }
14314 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14315 }
14316
14317 void
14318 AskQuestionEvent(title, question, replyPrefix, which)
14319      char *title; char *question; char *replyPrefix; char *which;
14320 {
14321   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14322   if (pr == NoProc) return;
14323   AskQuestion(title, question, replyPrefix, pr);
14324 }
14325
14326 void
14327 TypeInEvent(char firstChar)
14328 {
14329     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14330         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14331         gameMode == AnalyzeMode || gameMode == EditGame || \r
14332         gameMode == EditPosition || gameMode == IcsExamining ||\r
14333         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14334         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14335                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14336                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14337         gameMode == Training) PopUpMoveDialog(firstChar);
14338 }
14339
14340 void
14341 TypeInDoneEvent(char *move)
14342 {
14343         Board board;
14344         int n, fromX, fromY, toX, toY;
14345         char promoChar;
14346         ChessMove moveType;\r
14347
14348         // [HGM] FENedit\r
14349         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14350                 EditPositionPasteFEN(move);\r
14351                 return;\r
14352         }\r
14353         // [HGM] movenum: allow move number to be typed in any mode\r
14354         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14355           ToNrEvent(2*n-1);\r
14356           return;\r
14357         }\r
14358
14359       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14360         gameMode != Training) {\r
14361         DisplayMoveError(_("Displayed move is not current"));\r
14362       } else {\r
14363         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14364           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14365         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14366         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14367           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14368           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14369         } else {\r
14370           DisplayMoveError(_("Could not parse move"));\r
14371         }
14372       }\r
14373 }\r
14374
14375 void
14376 DisplayMove(moveNumber)
14377      int moveNumber;
14378 {
14379     char message[MSG_SIZ];
14380     char res[MSG_SIZ];
14381     char cpThinkOutput[MSG_SIZ];
14382
14383     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14384
14385     if (moveNumber == forwardMostMove - 1 ||
14386         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14387
14388         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14389
14390         if (strchr(cpThinkOutput, '\n')) {
14391             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14392         }
14393     } else {
14394         *cpThinkOutput = NULLCHAR;
14395     }
14396
14397     /* [AS] Hide thinking from human user */
14398     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14399         *cpThinkOutput = NULLCHAR;
14400         if( thinkOutput[0] != NULLCHAR ) {
14401             int i;
14402
14403             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14404                 cpThinkOutput[i] = '.';
14405             }
14406             cpThinkOutput[i] = NULLCHAR;
14407             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14408         }
14409     }
14410
14411     if (moveNumber == forwardMostMove - 1 &&
14412         gameInfo.resultDetails != NULL) {
14413         if (gameInfo.resultDetails[0] == NULLCHAR) {
14414           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14415         } else {
14416           snprintf(res, MSG_SIZ, " {%s} %s",
14417                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14418         }
14419     } else {
14420         res[0] = NULLCHAR;
14421     }
14422
14423     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14424         DisplayMessage(res, cpThinkOutput);
14425     } else {
14426       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14427                 WhiteOnMove(moveNumber) ? " " : ".. ",
14428                 parseList[moveNumber], res);
14429         DisplayMessage(message, cpThinkOutput);
14430     }
14431 }
14432
14433 void
14434 DisplayComment(moveNumber, text)
14435      int moveNumber;
14436      char *text;
14437 {
14438     char title[MSG_SIZ];
14439     char buf[8000]; // comment can be long!
14440     int score, depth;
14441
14442     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14443       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14444     } else {
14445       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14446               WhiteOnMove(moveNumber) ? " " : ".. ",
14447               parseList[moveNumber]);
14448     }
14449     // [HGM] PV info: display PV info together with (or as) comment
14450     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14451       if(text == NULL) text = "";
14452       score = pvInfoList[moveNumber].score;
14453       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14454               depth, (pvInfoList[moveNumber].time+50)/100, text);
14455       text = buf;
14456     }
14457     if (text != NULL && (appData.autoDisplayComment || commentUp))
14458         CommentPopUp(title, text);
14459 }
14460
14461 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14462  * might be busy thinking or pondering.  It can be omitted if your
14463  * gnuchess is configured to stop thinking immediately on any user
14464  * input.  However, that gnuchess feature depends on the FIONREAD
14465  * ioctl, which does not work properly on some flavors of Unix.
14466  */
14467 void
14468 Attention(cps)
14469      ChessProgramState *cps;
14470 {
14471 #if ATTENTION
14472     if (!cps->useSigint) return;
14473     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14474     switch (gameMode) {
14475       case MachinePlaysWhite:
14476       case MachinePlaysBlack:
14477       case TwoMachinesPlay:
14478       case IcsPlayingWhite:
14479       case IcsPlayingBlack:
14480       case AnalyzeMode:
14481       case AnalyzeFile:
14482         /* Skip if we know it isn't thinking */
14483         if (!cps->maybeThinking) return;
14484         if (appData.debugMode)
14485           fprintf(debugFP, "Interrupting %s\n", cps->which);
14486         InterruptChildProcess(cps->pr);
14487         cps->maybeThinking = FALSE;
14488         break;
14489       default:
14490         break;
14491     }
14492 #endif /*ATTENTION*/
14493 }
14494
14495 int
14496 CheckFlags()
14497 {
14498     if (whiteTimeRemaining <= 0) {
14499         if (!whiteFlag) {
14500             whiteFlag = TRUE;
14501             if (appData.icsActive) {
14502                 if (appData.autoCallFlag &&
14503                     gameMode == IcsPlayingBlack && !blackFlag) {
14504                   SendToICS(ics_prefix);
14505                   SendToICS("flag\n");
14506                 }
14507             } else {
14508                 if (blackFlag) {
14509                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14510                 } else {
14511                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14512                     if (appData.autoCallFlag) {
14513                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14514                         return TRUE;
14515                     }
14516                 }
14517             }
14518         }
14519     }
14520     if (blackTimeRemaining <= 0) {
14521         if (!blackFlag) {
14522             blackFlag = TRUE;
14523             if (appData.icsActive) {
14524                 if (appData.autoCallFlag &&
14525                     gameMode == IcsPlayingWhite && !whiteFlag) {
14526                   SendToICS(ics_prefix);
14527                   SendToICS("flag\n");
14528                 }
14529             } else {
14530                 if (whiteFlag) {
14531                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14532                 } else {
14533                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14534                     if (appData.autoCallFlag) {
14535                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14536                         return TRUE;
14537                     }
14538                 }
14539             }
14540         }
14541     }
14542     return FALSE;
14543 }
14544
14545 void
14546 CheckTimeControl()
14547 {
14548     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14549         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14550
14551     /*
14552      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14553      */
14554     if ( !WhiteOnMove(forwardMostMove) ) {
14555         /* White made time control */
14556         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14557         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14558         /* [HGM] time odds: correct new time quota for time odds! */
14559                                             / WhitePlayer()->timeOdds;
14560         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14561     } else {
14562         lastBlack -= blackTimeRemaining;
14563         /* Black made time control */
14564         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14565                                             / WhitePlayer()->other->timeOdds;
14566         lastWhite = whiteTimeRemaining;
14567     }
14568 }
14569
14570 void
14571 DisplayBothClocks()
14572 {
14573     int wom = gameMode == EditPosition ?
14574       !blackPlaysFirst : WhiteOnMove(currentMove);
14575     DisplayWhiteClock(whiteTimeRemaining, wom);
14576     DisplayBlackClock(blackTimeRemaining, !wom);
14577 }
14578
14579
14580 /* Timekeeping seems to be a portability nightmare.  I think everyone
14581    has ftime(), but I'm really not sure, so I'm including some ifdefs
14582    to use other calls if you don't.  Clocks will be less accurate if
14583    you have neither ftime nor gettimeofday.
14584 */
14585
14586 /* VS 2008 requires the #include outside of the function */
14587 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14588 #include <sys/timeb.h>
14589 #endif
14590
14591 /* Get the current time as a TimeMark */
14592 void
14593 GetTimeMark(tm)
14594      TimeMark *tm;
14595 {
14596 #if HAVE_GETTIMEOFDAY
14597
14598     struct timeval timeVal;
14599     struct timezone timeZone;
14600
14601     gettimeofday(&timeVal, &timeZone);
14602     tm->sec = (long) timeVal.tv_sec;
14603     tm->ms = (int) (timeVal.tv_usec / 1000L);
14604
14605 #else /*!HAVE_GETTIMEOFDAY*/
14606 #if HAVE_FTIME
14607
14608 // include <sys/timeb.h> / moved to just above start of function
14609     struct timeb timeB;
14610
14611     ftime(&timeB);
14612     tm->sec = (long) timeB.time;
14613     tm->ms = (int) timeB.millitm;
14614
14615 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14616     tm->sec = (long) time(NULL);
14617     tm->ms = 0;
14618 #endif
14619 #endif
14620 }
14621
14622 /* Return the difference in milliseconds between two
14623    time marks.  We assume the difference will fit in a long!
14624 */
14625 long
14626 SubtractTimeMarks(tm2, tm1)
14627      TimeMark *tm2, *tm1;
14628 {
14629     return 1000L*(tm2->sec - tm1->sec) +
14630            (long) (tm2->ms - tm1->ms);
14631 }
14632
14633
14634 /*
14635  * Code to manage the game clocks.
14636  *
14637  * In tournament play, black starts the clock and then white makes a move.
14638  * We give the human user a slight advantage if he is playing white---the
14639  * clocks don't run until he makes his first move, so it takes zero time.
14640  * Also, we don't account for network lag, so we could get out of sync
14641  * with GNU Chess's clock -- but then, referees are always right.
14642  */
14643
14644 static TimeMark tickStartTM;
14645 static long intendedTickLength;
14646
14647 long
14648 NextTickLength(timeRemaining)
14649      long timeRemaining;
14650 {
14651     long nominalTickLength, nextTickLength;
14652
14653     if (timeRemaining > 0L && timeRemaining <= 10000L)
14654       nominalTickLength = 100L;
14655     else
14656       nominalTickLength = 1000L;
14657     nextTickLength = timeRemaining % nominalTickLength;
14658     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14659
14660     return nextTickLength;
14661 }
14662
14663 /* Adjust clock one minute up or down */
14664 void
14665 AdjustClock(Boolean which, int dir)
14666 {
14667     if(which) blackTimeRemaining += 60000*dir;
14668     else      whiteTimeRemaining += 60000*dir;
14669     DisplayBothClocks();
14670 }
14671
14672 /* Stop clocks and reset to a fresh time control */
14673 void
14674 ResetClocks()
14675 {
14676     (void) StopClockTimer();
14677     if (appData.icsActive) {
14678         whiteTimeRemaining = blackTimeRemaining = 0;
14679     } else if (searchTime) {
14680         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14681         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14682     } else { /* [HGM] correct new time quote for time odds */
14683         whiteTC = blackTC = fullTimeControlString;
14684         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14685         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14686     }
14687     if (whiteFlag || blackFlag) {
14688         DisplayTitle("");
14689         whiteFlag = blackFlag = FALSE;
14690     }
14691     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14692     DisplayBothClocks();
14693 }
14694
14695 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14696
14697 /* Decrement running clock by amount of time that has passed */
14698 void
14699 DecrementClocks()
14700 {
14701     long timeRemaining;
14702     long lastTickLength, fudge;
14703     TimeMark now;
14704
14705     if (!appData.clockMode) return;
14706     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14707
14708     GetTimeMark(&now);
14709
14710     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14711
14712     /* Fudge if we woke up a little too soon */
14713     fudge = intendedTickLength - lastTickLength;
14714     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14715
14716     if (WhiteOnMove(forwardMostMove)) {
14717         if(whiteNPS >= 0) lastTickLength = 0;
14718         timeRemaining = whiteTimeRemaining -= lastTickLength;
14719         if(timeRemaining < 0 && !appData.icsActive) {
14720             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14721             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14722                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14723                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14724             }
14725         }
14726         DisplayWhiteClock(whiteTimeRemaining - fudge,
14727                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14728     } else {
14729         if(blackNPS >= 0) lastTickLength = 0;
14730         timeRemaining = blackTimeRemaining -= lastTickLength;
14731         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14732             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14733             if(suddenDeath) {
14734                 blackStartMove = forwardMostMove;
14735                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14736             }
14737         }
14738         DisplayBlackClock(blackTimeRemaining - fudge,
14739                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14740     }
14741     if (CheckFlags()) return;
14742
14743     tickStartTM = now;
14744     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14745     StartClockTimer(intendedTickLength);
14746
14747     /* if the time remaining has fallen below the alarm threshold, sound the
14748      * alarm. if the alarm has sounded and (due to a takeback or time control
14749      * with increment) the time remaining has increased to a level above the
14750      * threshold, reset the alarm so it can sound again.
14751      */
14752
14753     if (appData.icsActive && appData.icsAlarm) {
14754
14755         /* make sure we are dealing with the user's clock */
14756         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14757                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14758            )) return;
14759
14760         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14761             alarmSounded = FALSE;
14762         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14763             PlayAlarmSound();
14764             alarmSounded = TRUE;
14765         }
14766     }
14767 }
14768
14769
14770 /* A player has just moved, so stop the previously running
14771    clock and (if in clock mode) start the other one.
14772    We redisplay both clocks in case we're in ICS mode, because
14773    ICS gives us an update to both clocks after every move.
14774    Note that this routine is called *after* forwardMostMove
14775    is updated, so the last fractional tick must be subtracted
14776    from the color that is *not* on move now.
14777 */
14778 void
14779 SwitchClocks(int newMoveNr)
14780 {
14781     long lastTickLength;
14782     TimeMark now;
14783     int flagged = FALSE;
14784
14785     GetTimeMark(&now);
14786
14787     if (StopClockTimer() && appData.clockMode) {
14788         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14789         if (!WhiteOnMove(forwardMostMove)) {
14790             if(blackNPS >= 0) lastTickLength = 0;
14791             blackTimeRemaining -= lastTickLength;
14792            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14793 //         if(pvInfoList[forwardMostMove].time == -1)
14794                  pvInfoList[forwardMostMove].time =               // use GUI time
14795                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14796         } else {
14797            if(whiteNPS >= 0) lastTickLength = 0;
14798            whiteTimeRemaining -= lastTickLength;
14799            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14800 //         if(pvInfoList[forwardMostMove].time == -1)
14801                  pvInfoList[forwardMostMove].time =
14802                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14803         }
14804         flagged = CheckFlags();
14805     }
14806     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14807     CheckTimeControl();
14808
14809     if (flagged || !appData.clockMode) return;
14810
14811     switch (gameMode) {
14812       case MachinePlaysBlack:
14813       case MachinePlaysWhite:
14814       case BeginningOfGame:
14815         if (pausing) return;
14816         break;
14817
14818       case EditGame:
14819       case PlayFromGameFile:
14820       case IcsExamining:
14821         return;
14822
14823       default:
14824         break;
14825     }
14826
14827     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14828         if(WhiteOnMove(forwardMostMove))
14829              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14830         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14831     }
14832
14833     tickStartTM = now;
14834     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14835       whiteTimeRemaining : blackTimeRemaining);
14836     StartClockTimer(intendedTickLength);
14837 }
14838
14839
14840 /* Stop both clocks */
14841 void
14842 StopClocks()
14843 {
14844     long lastTickLength;
14845     TimeMark now;
14846
14847     if (!StopClockTimer()) return;
14848     if (!appData.clockMode) return;
14849
14850     GetTimeMark(&now);
14851
14852     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14853     if (WhiteOnMove(forwardMostMove)) {
14854         if(whiteNPS >= 0) lastTickLength = 0;
14855         whiteTimeRemaining -= lastTickLength;
14856         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14857     } else {
14858         if(blackNPS >= 0) lastTickLength = 0;
14859         blackTimeRemaining -= lastTickLength;
14860         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14861     }
14862     CheckFlags();
14863 }
14864
14865 /* Start clock of player on move.  Time may have been reset, so
14866    if clock is already running, stop and restart it. */
14867 void
14868 StartClocks()
14869 {
14870     (void) StopClockTimer(); /* in case it was running already */
14871     DisplayBothClocks();
14872     if (CheckFlags()) return;
14873
14874     if (!appData.clockMode) return;
14875     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14876
14877     GetTimeMark(&tickStartTM);
14878     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14879       whiteTimeRemaining : blackTimeRemaining);
14880
14881    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14882     whiteNPS = blackNPS = -1;
14883     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14884        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14885         whiteNPS = first.nps;
14886     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14887        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14888         blackNPS = first.nps;
14889     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14890         whiteNPS = second.nps;
14891     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14892         blackNPS = second.nps;
14893     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14894
14895     StartClockTimer(intendedTickLength);
14896 }
14897
14898 char *
14899 TimeString(ms)
14900      long ms;
14901 {
14902     long second, minute, hour, day;
14903     char *sign = "";
14904     static char buf[32];
14905
14906     if (ms > 0 && ms <= 9900) {
14907       /* convert milliseconds to tenths, rounding up */
14908       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14909
14910       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14911       return buf;
14912     }
14913
14914     /* convert milliseconds to seconds, rounding up */
14915     /* use floating point to avoid strangeness of integer division
14916        with negative dividends on many machines */
14917     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14918
14919     if (second < 0) {
14920         sign = "-";
14921         second = -second;
14922     }
14923
14924     day = second / (60 * 60 * 24);
14925     second = second % (60 * 60 * 24);
14926     hour = second / (60 * 60);
14927     second = second % (60 * 60);
14928     minute = second / 60;
14929     second = second % 60;
14930
14931     if (day > 0)
14932       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14933               sign, day, hour, minute, second);
14934     else if (hour > 0)
14935       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14936     else
14937       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14938
14939     return buf;
14940 }
14941
14942
14943 /*
14944  * This is necessary because some C libraries aren't ANSI C compliant yet.
14945  */
14946 char *
14947 StrStr(string, match)
14948      char *string, *match;
14949 {
14950     int i, length;
14951
14952     length = strlen(match);
14953
14954     for (i = strlen(string) - length; i >= 0; i--, string++)
14955       if (!strncmp(match, string, length))
14956         return string;
14957
14958     return NULL;
14959 }
14960
14961 char *
14962 StrCaseStr(string, match)
14963      char *string, *match;
14964 {
14965     int i, j, length;
14966
14967     length = strlen(match);
14968
14969     for (i = strlen(string) - length; i >= 0; i--, string++) {
14970         for (j = 0; j < length; j++) {
14971             if (ToLower(match[j]) != ToLower(string[j]))
14972               break;
14973         }
14974         if (j == length) return string;
14975     }
14976
14977     return NULL;
14978 }
14979
14980 #ifndef _amigados
14981 int
14982 StrCaseCmp(s1, s2)
14983      char *s1, *s2;
14984 {
14985     char c1, c2;
14986
14987     for (;;) {
14988         c1 = ToLower(*s1++);
14989         c2 = ToLower(*s2++);
14990         if (c1 > c2) return 1;
14991         if (c1 < c2) return -1;
14992         if (c1 == NULLCHAR) return 0;
14993     }
14994 }
14995
14996
14997 int
14998 ToLower(c)
14999      int c;
15000 {
15001     return isupper(c) ? tolower(c) : c;
15002 }
15003
15004
15005 int
15006 ToUpper(c)
15007      int c;
15008 {
15009     return islower(c) ? toupper(c) : c;
15010 }
15011 #endif /* !_amigados    */
15012
15013 char *
15014 StrSave(s)
15015      char *s;
15016 {
15017   char *ret;
15018
15019   if ((ret = (char *) malloc(strlen(s) + 1)))
15020     {
15021       safeStrCpy(ret, s, strlen(s)+1);
15022     }
15023   return ret;
15024 }
15025
15026 char *
15027 StrSavePtr(s, savePtr)
15028      char *s, **savePtr;
15029 {
15030     if (*savePtr) {
15031         free(*savePtr);
15032     }
15033     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15034       safeStrCpy(*savePtr, s, strlen(s)+1);
15035     }
15036     return(*savePtr);
15037 }
15038
15039 char *
15040 PGNDate()
15041 {
15042     time_t clock;
15043     struct tm *tm;
15044     char buf[MSG_SIZ];
15045
15046     clock = time((time_t *)NULL);
15047     tm = localtime(&clock);
15048     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15049             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15050     return StrSave(buf);
15051 }
15052
15053
15054 char *
15055 PositionToFEN(move, overrideCastling)
15056      int move;
15057      char *overrideCastling;
15058 {
15059     int i, j, fromX, fromY, toX, toY;
15060     int whiteToPlay;
15061     char buf[128];
15062     char *p, *q;
15063     int emptycount;
15064     ChessSquare piece;
15065
15066     whiteToPlay = (gameMode == EditPosition) ?
15067       !blackPlaysFirst : (move % 2 == 0);
15068     p = buf;
15069
15070     /* Piece placement data */
15071     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15072         emptycount = 0;
15073         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15074             if (boards[move][i][j] == EmptySquare) {
15075                 emptycount++;
15076             } else { ChessSquare piece = boards[move][i][j];
15077                 if (emptycount > 0) {
15078                     if(emptycount<10) /* [HGM] can be >= 10 */
15079                         *p++ = '0' + emptycount;
15080                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15081                     emptycount = 0;
15082                 }
15083                 if(PieceToChar(piece) == '+') {
15084                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15085                     *p++ = '+';
15086                     piece = (ChessSquare)(DEMOTED piece);
15087                 }
15088                 *p++ = PieceToChar(piece);
15089                 if(p[-1] == '~') {
15090                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15091                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15092                     *p++ = '~';
15093                 }
15094             }
15095         }
15096         if (emptycount > 0) {
15097             if(emptycount<10) /* [HGM] can be >= 10 */
15098                 *p++ = '0' + emptycount;
15099             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15100             emptycount = 0;
15101         }
15102         *p++ = '/';
15103     }
15104     *(p - 1) = ' ';
15105
15106     /* [HGM] print Crazyhouse or Shogi holdings */
15107     if( gameInfo.holdingsWidth ) {
15108         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15109         q = p;
15110         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15111             piece = boards[move][i][BOARD_WIDTH-1];
15112             if( piece != EmptySquare )
15113               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15114                   *p++ = PieceToChar(piece);
15115         }
15116         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15117             piece = boards[move][BOARD_HEIGHT-i-1][0];
15118             if( piece != EmptySquare )
15119               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15120                   *p++ = PieceToChar(piece);
15121         }
15122
15123         if( q == p ) *p++ = '-';
15124         *p++ = ']';
15125         *p++ = ' ';
15126     }
15127
15128     /* Active color */
15129     *p++ = whiteToPlay ? 'w' : 'b';
15130     *p++ = ' ';
15131
15132   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15133     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15134   } else {
15135   if(nrCastlingRights) {
15136      q = p;
15137      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15138        /* [HGM] write directly from rights */
15139            if(boards[move][CASTLING][2] != NoRights &&
15140               boards[move][CASTLING][0] != NoRights   )
15141                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15142            if(boards[move][CASTLING][2] != NoRights &&
15143               boards[move][CASTLING][1] != NoRights   )
15144                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15145            if(boards[move][CASTLING][5] != NoRights &&
15146               boards[move][CASTLING][3] != NoRights   )
15147                 *p++ = boards[move][CASTLING][3] + AAA;
15148            if(boards[move][CASTLING][5] != NoRights &&
15149               boards[move][CASTLING][4] != NoRights   )
15150                 *p++ = boards[move][CASTLING][4] + AAA;
15151      } else {
15152
15153         /* [HGM] write true castling rights */
15154         if( nrCastlingRights == 6 ) {
15155             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15156                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15157             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15158                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15159             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15160                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15161             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15162                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15163         }
15164      }
15165      if (q == p) *p++ = '-'; /* No castling rights */
15166      *p++ = ' ';
15167   }
15168
15169   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15170      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15171     /* En passant target square */
15172     if (move > backwardMostMove) {
15173         fromX = moveList[move - 1][0] - AAA;
15174         fromY = moveList[move - 1][1] - ONE;
15175         toX = moveList[move - 1][2] - AAA;
15176         toY = moveList[move - 1][3] - ONE;
15177         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15178             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15179             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15180             fromX == toX) {
15181             /* 2-square pawn move just happened */
15182             *p++ = toX + AAA;
15183             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15184         } else {
15185             *p++ = '-';
15186         }
15187     } else if(move == backwardMostMove) {
15188         // [HGM] perhaps we should always do it like this, and forget the above?
15189         if((signed char)boards[move][EP_STATUS] >= 0) {
15190             *p++ = boards[move][EP_STATUS] + AAA;
15191             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15192         } else {
15193             *p++ = '-';
15194         }
15195     } else {
15196         *p++ = '-';
15197     }
15198     *p++ = ' ';
15199   }
15200   }
15201
15202     /* [HGM] find reversible plies */
15203     {   int i = 0, j=move;
15204
15205         if (appData.debugMode) { int k;
15206             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15207             for(k=backwardMostMove; k<=forwardMostMove; k++)
15208                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15209
15210         }
15211
15212         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15213         if( j == backwardMostMove ) i += initialRulePlies;
15214         sprintf(p, "%d ", i);
15215         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15216     }
15217     /* Fullmove number */
15218     sprintf(p, "%d", (move / 2) + 1);
15219
15220     return StrSave(buf);
15221 }
15222
15223 Boolean
15224 ParseFEN(board, blackPlaysFirst, fen)
15225     Board board;
15226      int *blackPlaysFirst;
15227      char *fen;
15228 {
15229     int i, j;
15230     char *p, c;
15231     int emptycount;
15232     ChessSquare piece;
15233
15234     p = fen;
15235
15236     /* [HGM] by default clear Crazyhouse holdings, if present */
15237     if(gameInfo.holdingsWidth) {
15238        for(i=0; i<BOARD_HEIGHT; i++) {
15239            board[i][0]             = EmptySquare; /* black holdings */
15240            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15241            board[i][1]             = (ChessSquare) 0; /* black counts */
15242            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15243        }
15244     }
15245
15246     /* Piece placement data */
15247     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15248         j = 0;
15249         for (;;) {
15250             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15251                 if (*p == '/') p++;
15252                 emptycount = gameInfo.boardWidth - j;
15253                 while (emptycount--)
15254                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15255                 break;
15256 #if(BOARD_FILES >= 10)
15257             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15258                 p++; emptycount=10;
15259                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15260                 while (emptycount--)
15261                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15262 #endif
15263             } else if (isdigit(*p)) {
15264                 emptycount = *p++ - '0';
15265                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15266                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15267                 while (emptycount--)
15268                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15269             } else if (*p == '+' || isalpha(*p)) {
15270                 if (j >= gameInfo.boardWidth) return FALSE;
15271                 if(*p=='+') {
15272                     piece = CharToPiece(*++p);
15273                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15274                     piece = (ChessSquare) (PROMOTED piece ); p++;
15275                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15276                 } else piece = CharToPiece(*p++);
15277
15278                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15279                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15280                     piece = (ChessSquare) (PROMOTED piece);
15281                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15282                     p++;
15283                 }
15284                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15285             } else {
15286                 return FALSE;
15287             }
15288         }
15289     }
15290     while (*p == '/' || *p == ' ') p++;
15291
15292     /* [HGM] look for Crazyhouse holdings here */
15293     while(*p==' ') p++;
15294     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15295         if(*p == '[') p++;
15296         if(*p == '-' ) p++; /* empty holdings */ else {
15297             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15298             /* if we would allow FEN reading to set board size, we would   */
15299             /* have to add holdings and shift the board read so far here   */
15300             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15301                 p++;
15302                 if((int) piece >= (int) BlackPawn ) {
15303                     i = (int)piece - (int)BlackPawn;
15304                     i = PieceToNumber((ChessSquare)i);
15305                     if( i >= gameInfo.holdingsSize ) return FALSE;
15306                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15307                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15308                 } else {
15309                     i = (int)piece - (int)WhitePawn;
15310                     i = PieceToNumber((ChessSquare)i);
15311                     if( i >= gameInfo.holdingsSize ) return FALSE;
15312                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15313                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15314                 }
15315             }
15316         }
15317         if(*p == ']') p++;
15318     }
15319
15320     while(*p == ' ') p++;
15321
15322     /* Active color */
15323     c = *p++;
15324     if(appData.colorNickNames) {
15325       if( c == appData.colorNickNames[0] ) c = 'w'; else
15326       if( c == appData.colorNickNames[1] ) c = 'b';
15327     }
15328     switch (c) {
15329       case 'w':
15330         *blackPlaysFirst = FALSE;
15331         break;
15332       case 'b':
15333         *blackPlaysFirst = TRUE;
15334         break;
15335       default:
15336         return FALSE;
15337     }
15338
15339     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15340     /* return the extra info in global variiables             */
15341
15342     /* set defaults in case FEN is incomplete */
15343     board[EP_STATUS] = EP_UNKNOWN;
15344     for(i=0; i<nrCastlingRights; i++ ) {
15345         board[CASTLING][i] =
15346             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15347     }   /* assume possible unless obviously impossible */
15348     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15349     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15350     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15351                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15352     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15353     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15354     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15355                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15356     FENrulePlies = 0;
15357
15358     while(*p==' ') p++;
15359     if(nrCastlingRights) {
15360       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15361           /* castling indicator present, so default becomes no castlings */
15362           for(i=0; i<nrCastlingRights; i++ ) {
15363                  board[CASTLING][i] = NoRights;
15364           }
15365       }
15366       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15367              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15368              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15369              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15370         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15371
15372         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15373             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15374             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15375         }
15376         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15377             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15378         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15379                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15380         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15381                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15382         switch(c) {
15383           case'K':
15384               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15385               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15386               board[CASTLING][2] = whiteKingFile;
15387               break;
15388           case'Q':
15389               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15390               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15391               board[CASTLING][2] = whiteKingFile;
15392               break;
15393           case'k':
15394               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15395               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15396               board[CASTLING][5] = blackKingFile;
15397               break;
15398           case'q':
15399               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15400               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15401               board[CASTLING][5] = blackKingFile;
15402           case '-':
15403               break;
15404           default: /* FRC castlings */
15405               if(c >= 'a') { /* black rights */
15406                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15407                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15408                   if(i == BOARD_RGHT) break;
15409                   board[CASTLING][5] = i;
15410                   c -= AAA;
15411                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15412                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15413                   if(c > i)
15414                       board[CASTLING][3] = c;
15415                   else
15416                       board[CASTLING][4] = c;
15417               } else { /* white rights */
15418                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15419                     if(board[0][i] == WhiteKing) break;
15420                   if(i == BOARD_RGHT) break;
15421                   board[CASTLING][2] = i;
15422                   c -= AAA - 'a' + 'A';
15423                   if(board[0][c] >= WhiteKing) break;
15424                   if(c > i)
15425                       board[CASTLING][0] = c;
15426                   else
15427                       board[CASTLING][1] = c;
15428               }
15429         }
15430       }
15431       for(i=0; i<nrCastlingRights; i++)
15432         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15433     if (appData.debugMode) {
15434         fprintf(debugFP, "FEN castling rights:");
15435         for(i=0; i<nrCastlingRights; i++)
15436         fprintf(debugFP, " %d", board[CASTLING][i]);
15437         fprintf(debugFP, "\n");
15438     }
15439
15440       while(*p==' ') p++;
15441     }
15442
15443     /* read e.p. field in games that know e.p. capture */
15444     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15445        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15446       if(*p=='-') {
15447         p++; board[EP_STATUS] = EP_NONE;
15448       } else {
15449          char c = *p++ - AAA;
15450
15451          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15452          if(*p >= '0' && *p <='9') p++;
15453          board[EP_STATUS] = c;
15454       }
15455     }
15456
15457
15458     if(sscanf(p, "%d", &i) == 1) {
15459         FENrulePlies = i; /* 50-move ply counter */
15460         /* (The move number is still ignored)    */
15461     }
15462
15463     return TRUE;
15464 }
15465
15466 void
15467 EditPositionPasteFEN(char *fen)
15468 {
15469   if (fen != NULL) {
15470     Board initial_position;
15471
15472     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15473       DisplayError(_("Bad FEN position in clipboard"), 0);
15474       return ;
15475     } else {
15476       int savedBlackPlaysFirst = blackPlaysFirst;
15477       EditPositionEvent();
15478       blackPlaysFirst = savedBlackPlaysFirst;
15479       CopyBoard(boards[0], initial_position);
15480       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15481       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15482       DisplayBothClocks();
15483       DrawPosition(FALSE, boards[currentMove]);
15484     }
15485   }
15486 }
15487
15488 static char cseq[12] = "\\   ";
15489
15490 Boolean set_cont_sequence(char *new_seq)
15491 {
15492     int len;
15493     Boolean ret;
15494
15495     // handle bad attempts to set the sequence
15496         if (!new_seq)
15497                 return 0; // acceptable error - no debug
15498
15499     len = strlen(new_seq);
15500     ret = (len > 0) && (len < sizeof(cseq));
15501     if (ret)
15502       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15503     else if (appData.debugMode)
15504       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15505     return ret;
15506 }
15507
15508 /*
15509     reformat a source message so words don't cross the width boundary.  internal
15510     newlines are not removed.  returns the wrapped size (no null character unless
15511     included in source message).  If dest is NULL, only calculate the size required
15512     for the dest buffer.  lp argument indicats line position upon entry, and it's
15513     passed back upon exit.
15514 */
15515 int wrap(char *dest, char *src, int count, int width, int *lp)
15516 {
15517     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15518
15519     cseq_len = strlen(cseq);
15520     old_line = line = *lp;
15521     ansi = len = clen = 0;
15522
15523     for (i=0; i < count; i++)
15524     {
15525         if (src[i] == '\033')
15526             ansi = 1;
15527
15528         // if we hit the width, back up
15529         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15530         {
15531             // store i & len in case the word is too long
15532             old_i = i, old_len = len;
15533
15534             // find the end of the last word
15535             while (i && src[i] != ' ' && src[i] != '\n')
15536             {
15537                 i--;
15538                 len--;
15539             }
15540
15541             // word too long?  restore i & len before splitting it
15542             if ((old_i-i+clen) >= width)
15543             {
15544                 i = old_i;
15545                 len = old_len;
15546             }
15547
15548             // extra space?
15549             if (i && src[i-1] == ' ')
15550                 len--;
15551
15552             if (src[i] != ' ' && src[i] != '\n')
15553             {
15554                 i--;
15555                 if (len)
15556                     len--;
15557             }
15558
15559             // now append the newline and continuation sequence
15560             if (dest)
15561                 dest[len] = '\n';
15562             len++;
15563             if (dest)
15564                 strncpy(dest+len, cseq, cseq_len);
15565             len += cseq_len;
15566             line = cseq_len;
15567             clen = cseq_len;
15568             continue;
15569         }
15570
15571         if (dest)
15572             dest[len] = src[i];
15573         len++;
15574         if (!ansi)
15575             line++;
15576         if (src[i] == '\n')
15577             line = 0;
15578         if (src[i] == 'm')
15579             ansi = 0;
15580     }
15581     if (dest && appData.debugMode)
15582     {
15583         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15584             count, width, line, len, *lp);
15585         show_bytes(debugFP, src, count);
15586         fprintf(debugFP, "\ndest: ");
15587         show_bytes(debugFP, dest, len);
15588         fprintf(debugFP, "\n");
15589     }
15590     *lp = dest ? line : old_line;
15591
15592     return len;
15593 }
15594
15595 // [HGM] vari: routines for shelving variations
15596
15597 void
15598 PushTail(int firstMove, int lastMove)
15599 {
15600         int i, j, nrMoves = lastMove - firstMove;
15601
15602         if(appData.icsActive) { // only in local mode
15603                 forwardMostMove = currentMove; // mimic old ICS behavior
15604                 return;
15605         }
15606         if(storedGames >= MAX_VARIATIONS-1) return;
15607
15608         // push current tail of game on stack
15609         savedResult[storedGames] = gameInfo.result;
15610         savedDetails[storedGames] = gameInfo.resultDetails;
15611         gameInfo.resultDetails = NULL;
15612         savedFirst[storedGames] = firstMove;
15613         savedLast [storedGames] = lastMove;
15614         savedFramePtr[storedGames] = framePtr;
15615         framePtr -= nrMoves; // reserve space for the boards
15616         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15617             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15618             for(j=0; j<MOVE_LEN; j++)
15619                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15620             for(j=0; j<2*MOVE_LEN; j++)
15621                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15622             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15623             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15624             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15625             pvInfoList[firstMove+i-1].depth = 0;
15626             commentList[framePtr+i] = commentList[firstMove+i];
15627             commentList[firstMove+i] = NULL;
15628         }
15629
15630         storedGames++;
15631         forwardMostMove = firstMove; // truncate game so we can start variation
15632         if(storedGames == 1) GreyRevert(FALSE);
15633 }
15634
15635 Boolean
15636 PopTail(Boolean annotate)
15637 {
15638         int i, j, nrMoves;
15639         char buf[8000], moveBuf[20];
15640
15641         if(appData.icsActive) return FALSE; // only in local mode
15642         if(!storedGames) return FALSE; // sanity
15643         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15644
15645         storedGames--;
15646         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15647         nrMoves = savedLast[storedGames] - currentMove;
15648         if(annotate) {
15649                 int cnt = 10;
15650                 if(!WhiteOnMove(currentMove))
15651                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15652                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15653                 for(i=currentMove; i<forwardMostMove; i++) {
15654                         if(WhiteOnMove(i))
15655                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15656                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15657                         strcat(buf, moveBuf);
15658                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15659                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15660                 }
15661                 strcat(buf, ")");
15662         }
15663         for(i=1; i<=nrMoves; i++) { // copy last variation back
15664             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15665             for(j=0; j<MOVE_LEN; j++)
15666                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15667             for(j=0; j<2*MOVE_LEN; j++)
15668                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15669             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15670             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15671             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15672             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15673             commentList[currentMove+i] = commentList[framePtr+i];
15674             commentList[framePtr+i] = NULL;
15675         }
15676         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15677         framePtr = savedFramePtr[storedGames];
15678         gameInfo.result = savedResult[storedGames];
15679         if(gameInfo.resultDetails != NULL) {
15680             free(gameInfo.resultDetails);
15681       }
15682         gameInfo.resultDetails = savedDetails[storedGames];
15683         forwardMostMove = currentMove + nrMoves;
15684         if(storedGames == 0) GreyRevert(TRUE);
15685         return TRUE;
15686 }
15687
15688 void
15689 CleanupTail()
15690 {       // remove all shelved variations
15691         int i;
15692         for(i=0; i<storedGames; i++) {
15693             if(savedDetails[i])
15694                 free(savedDetails[i]);
15695             savedDetails[i] = NULL;
15696         }
15697         for(i=framePtr; i<MAX_MOVES; i++) {
15698                 if(commentList[i]) free(commentList[i]);
15699                 commentList[i] = NULL;
15700         }
15701         framePtr = MAX_MOVES-1;
15702         storedGames = 0;
15703 }
15704
15705 void
15706 LoadVariation(int index, char *text)
15707 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15708         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15709         int level = 0, move;
15710
15711         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15712         // first find outermost bracketing variation
15713         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15714             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15715                 if(*p == '{') wait = '}'; else
15716                 if(*p == '[') wait = ']'; else
15717                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15718                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15719             }
15720             if(*p == wait) wait = NULLCHAR; // closing ]} found
15721             p++;
15722         }
15723         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15724         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15725         end[1] = NULLCHAR; // clip off comment beyond variation
15726         ToNrEvent(currentMove-1);
15727         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15728         // kludge: use ParsePV() to append variation to game
15729         move = currentMove;
15730         ParsePV(start, TRUE);
15731         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15732         ClearPremoveHighlights();
15733         CommentPopDown();
15734         ToNrEvent(currentMove+1);
15735 }
15736