Describe -sweepPromotions in texi file
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy( char *dst, const char *src, size_t count )
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble(u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags(index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
527 };
528
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
534 };
535
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
541 };
542
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackMan, BlackFerz,
547         BlackKing, BlackMan, BlackKnight, BlackRook }
548 };
549
550
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
557 };
558
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
564 };
565
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
571 };
572
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
578 };
579
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
585 };
586
587 #ifdef GOTHIC
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
593 };
594 #else // !GOTHIC
595 #define GothicArray CapablancaArray
596 #endif // !GOTHIC
597
598 #ifdef FALCON
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !FALCON
606 #define FalconArray CapablancaArray
607 #endif // !FALCON
608
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
615
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 };
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
626
627
628 Board initialPosition;
629
630
631 /* Convert str to a rating. Checks for special cases of "----",
632
633    "++++", etc. Also strips ()'s */
634 int
635 string_to_rating(str)
636   char *str;
637 {
638   while(*str && !isdigit(*str)) ++str;
639   if (!*str)
640     return 0;   /* One of the special "no rating" cases */
641   else
642     return atoi(str);
643 }
644
645 void
646 ClearProgramStats()
647 {
648     /* Init programStats */
649     programStats.movelist[0] = 0;
650     programStats.depth = 0;
651     programStats.nr_moves = 0;
652     programStats.moves_left = 0;
653     programStats.nodes = 0;
654     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
655     programStats.score = 0;
656     programStats.got_only_move = 0;
657     programStats.got_fail = 0;
658     programStats.line_is_book = 0;
659 }
660
661 void
662 InitBackEnd1()
663 {
664     int matched, min, sec;
665
666     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
667     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
668
669     GetTimeMark(&programStartTime);
670     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
671
672     ClearProgramStats();
673     programStats.ok_to_send = 1;
674     programStats.seen_stat = 0;
675
676     /*
677      * Initialize game list
678      */
679     ListNew(&gameList);
680
681
682     /*
683      * Internet chess server status
684      */
685     if (appData.icsActive) {
686         appData.matchMode = FALSE;
687         appData.matchGames = 0;
688 #if ZIPPY
689         appData.noChessProgram = !appData.zippyPlay;
690 #else
691         appData.zippyPlay = FALSE;
692         appData.zippyTalk = FALSE;
693         appData.noChessProgram = TRUE;
694 #endif
695         if (*appData.icsHelper != NULLCHAR) {
696             appData.useTelnet = TRUE;
697             appData.telnetProgram = appData.icsHelper;
698         }
699     } else {
700         appData.zippyTalk = appData.zippyPlay = FALSE;
701     }
702
703     /* [AS] Initialize pv info list [HGM] and game state */
704     {
705         int i, j;
706
707         for( i=0; i<=framePtr; i++ ) {
708             pvInfoList[i].depth = -1;
709             boards[i][EP_STATUS] = EP_NONE;
710             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
711         }
712     }
713
714     /*
715      * Parse timeControl resource
716      */
717     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
718                           appData.movesPerSession)) {
719         char buf[MSG_SIZ];
720         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
721         DisplayFatalError(buf, 0, 2);
722     }
723
724     /*
725      * Parse searchTime resource
726      */
727     if (*appData.searchTime != NULLCHAR) {
728         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
729         if (matched == 1) {
730             searchTime = min * 60;
731         } else if (matched == 2) {
732             searchTime = min * 60 + sec;
733         } else {
734             char buf[MSG_SIZ];
735             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
736             DisplayFatalError(buf, 0, 2);
737         }
738     }
739
740     /* [AS] Adjudication threshold */
741     adjudicateLossThreshold = appData.adjudicateLossThreshold;
742
743     first.which = "first";
744     second.which = "second";
745     first.maybeThinking = second.maybeThinking = FALSE;
746     first.pr = second.pr = NoProc;
747     first.isr = second.isr = NULL;
748     first.sendTime = second.sendTime = 2;
749     first.sendDrawOffers = 1;
750     if (appData.firstPlaysBlack) {
751         first.twoMachinesColor = "black\n";
752         second.twoMachinesColor = "white\n";
753     } else {
754         first.twoMachinesColor = "white\n";
755         second.twoMachinesColor = "black\n";
756     }
757     first.program = appData.firstChessProgram;
758     second.program = appData.secondChessProgram;
759     first.host = appData.firstHost;
760     second.host = appData.secondHost;
761     first.dir = appData.firstDirectory;
762     second.dir = appData.secondDirectory;
763     first.other = &second;
764     second.other = &first;
765     first.initString = appData.initString;
766     second.initString = appData.secondInitString;
767     first.computerString = appData.firstComputerString;
768     second.computerString = appData.secondComputerString;
769     first.useSigint = second.useSigint = TRUE;
770     first.useSigterm = second.useSigterm = TRUE;
771     first.reuse = appData.reuseFirst;
772     second.reuse = appData.reuseSecond;
773     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
774     second.nps = appData.secondNPS;
775     first.useSetboard = second.useSetboard = FALSE;
776     first.useSAN = second.useSAN = FALSE;
777     first.usePing = second.usePing = FALSE;
778     first.lastPing = second.lastPing = 0;
779     first.lastPong = second.lastPong = 0;
780     first.usePlayother = second.usePlayother = FALSE;
781     first.useColors = second.useColors = TRUE;
782     first.useUsermove = second.useUsermove = FALSE;
783     first.sendICS = second.sendICS = FALSE;
784     first.sendName = second.sendName = appData.icsActive;
785     first.sdKludge = second.sdKludge = FALSE;
786     first.stKludge = second.stKludge = FALSE;
787     TidyProgramName(first.program, first.host, first.tidy);
788     TidyProgramName(second.program, second.host, second.tidy);
789     first.matchWins = second.matchWins = 0;
790     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
791     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
792     first.analysisSupport = second.analysisSupport = 2; /* detect */
793     first.analyzing = second.analyzing = FALSE;
794     first.initDone = second.initDone = FALSE;
795
796     /* New features added by Tord: */
797     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
798     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
799     /* End of new features added by Tord. */
800     first.fenOverride  = appData.fenOverride1;
801     second.fenOverride = appData.fenOverride2;
802
803     /* [HGM] time odds: set factor for each machine */
804     first.timeOdds  = appData.firstTimeOdds;
805     second.timeOdds = appData.secondTimeOdds;
806     { float norm = 1;
807         if(appData.timeOddsMode) {
808             norm = first.timeOdds;
809             if(norm > second.timeOdds) norm = second.timeOdds;
810         }
811         first.timeOdds /= norm;
812         second.timeOdds /= norm;
813     }
814
815     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
816     first.accumulateTC = appData.firstAccumulateTC;
817     second.accumulateTC = appData.secondAccumulateTC;
818     first.maxNrOfSessions = second.maxNrOfSessions = 1;
819
820     /* [HGM] debug */
821     first.debug = second.debug = FALSE;
822     first.supportsNPS = second.supportsNPS = UNKNOWN;
823
824     /* [HGM] options */
825     first.optionSettings  = appData.firstOptions;
826     second.optionSettings = appData.secondOptions;
827
828     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
829     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
830     first.isUCI = appData.firstIsUCI; /* [AS] */
831     second.isUCI = appData.secondIsUCI; /* [AS] */
832     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
833     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
834
835     if (appData.firstProtocolVersion > PROTOVER
836         || appData.firstProtocolVersion < 1)
837       {
838         char buf[MSG_SIZ];
839         int len;
840
841         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
842                        appData.firstProtocolVersion);
843         if( (len > MSG_SIZ) && appData.debugMode )
844           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
845
846         DisplayFatalError(buf, 0, 2);
847       }
848     else
849       {
850         first.protocolVersion = appData.firstProtocolVersion;
851       }
852
853     if (appData.secondProtocolVersion > PROTOVER
854         || appData.secondProtocolVersion < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.secondProtocolVersion);
861         if( (len > MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         second.protocolVersion = appData.secondProtocolVersion;
869       }
870
871     if (appData.icsActive) {
872         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
873 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
874     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
875         appData.clockMode = FALSE;
876         first.sendTime = second.sendTime = 0;
877     }
878
879 #if ZIPPY
880     /* Override some settings from environment variables, for backward
881        compatibility.  Unfortunately it's not feasible to have the env
882        vars just set defaults, at least in xboard.  Ugh.
883     */
884     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
885       ZippyInit();
886     }
887 #endif
888
889     if (appData.noChessProgram) {
890         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
891         sprintf(programVersion, "%s", PACKAGE_STRING);
892     } else {
893       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
894       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
895       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
896     }
897
898     if (!appData.icsActive) {
899       char buf[MSG_SIZ];
900       int len;
901
902       /* Check for variants that are supported only in ICS mode,
903          or not at all.  Some that are accepted here nevertheless
904          have bugs; see comments below.
905       */
906       VariantClass variant = StringToVariant(appData.variant);
907       switch (variant) {
908       case VariantBughouse:     /* need four players and two boards */
909       case VariantKriegspiel:   /* need to hide pieces and move details */
910         /* case VariantFischeRandom: (Fabien: moved below) */
911         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
912         if( (len > MSG_SIZ) && appData.debugMode )
913           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
914
915         DisplayFatalError(buf, 0, 2);
916         return;
917
918       case VariantUnknown:
919       case VariantLoadable:
920       case Variant29:
921       case Variant30:
922       case Variant31:
923       case Variant32:
924       case Variant33:
925       case Variant34:
926       case Variant35:
927       case Variant36:
928       default:
929         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
930         if( (len > MSG_SIZ) && appData.debugMode )
931           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
932
933         DisplayFatalError(buf, 0, 2);
934         return;
935
936       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
937       case VariantFairy:      /* [HGM] TestLegality definitely off! */
938       case VariantGothic:     /* [HGM] should work */
939       case VariantCapablanca: /* [HGM] should work */
940       case VariantCourier:    /* [HGM] initial forced moves not implemented */
941       case VariantShogi:      /* [HGM] could still mate with pawn drop */
942       case VariantKnightmate: /* [HGM] should work */
943       case VariantCylinder:   /* [HGM] untested */
944       case VariantFalcon:     /* [HGM] untested */
945       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
946                                  offboard interposition not understood */
947       case VariantNormal:     /* definitely works! */
948       case VariantWildCastle: /* pieces not automatically shuffled */
949       case VariantNoCastle:   /* pieces not automatically shuffled */
950       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
951       case VariantLosers:     /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantSuicide:    /* should work except for win condition,
954                                  and doesn't know captures are mandatory */
955       case VariantGiveaway:   /* should work except for win condition,
956                                  and doesn't know captures are mandatory */
957       case VariantTwoKings:   /* should work */
958       case VariantAtomic:     /* should work except for win condition */
959       case Variant3Check:     /* should work except for win condition */
960       case VariantShatranj:   /* should work except for all win conditions */
961       case VariantMakruk:     /* should work except for daw countdown */
962       case VariantBerolina:   /* might work if TestLegality is off */
963       case VariantCapaRandom: /* should work */
964       case VariantJanus:      /* should work */
965       case VariantSuper:      /* experimental */
966       case VariantGreat:      /* experimental, requires legality testing to be off */
967       case VariantSChess:     /* S-Chess, should work */
968       case VariantSpartan:    /* should work */
969         break;
970       }
971     }
972
973     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
974     InitEngineUCI( installDir, &second );
975 }
976
977 int NextIntegerFromString( char ** str, long * value )
978 {
979     int result = -1;
980     char * s = *str;
981
982     while( *s == ' ' || *s == '\t' ) {
983         s++;
984     }
985
986     *value = 0;
987
988     if( *s >= '0' && *s <= '9' ) {
989         while( *s >= '0' && *s <= '9' ) {
990             *value = *value * 10 + (*s - '0');
991             s++;
992         }
993
994         result = 0;
995     }
996
997     *str = s;
998
999     return result;
1000 }
1001
1002 int NextTimeControlFromString( char ** str, long * value )
1003 {
1004     long temp;
1005     int result = NextIntegerFromString( str, &temp );
1006
1007     if( result == 0 ) {
1008         *value = temp * 60; /* Minutes */
1009         if( **str == ':' ) {
1010             (*str)++;
1011             result = NextIntegerFromString( str, &temp );
1012             *value += temp; /* Seconds */
1013         }
1014     }
1015
1016     return result;
1017 }
1018
1019 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1020 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1021     int result = -1, type = 0; long temp, temp2;
1022
1023     if(**str != ':') return -1; // old params remain in force!
1024     (*str)++;
1025     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1026     if( NextIntegerFromString( str, &temp ) ) return -1;
1027     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1028
1029     if(**str != '/') {
1030         /* time only: incremental or sudden-death time control */
1031         if(**str == '+') { /* increment follows; read it */
1032             (*str)++;
1033             if(**str == '!') type = *(*str)++; // Bronstein TC
1034             if(result = NextIntegerFromString( str, &temp2)) return -1;
1035             *inc = temp2 * 1000;
1036             if(**str == '.') { // read fraction of increment
1037                 char *start = ++(*str);
1038                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1039                 temp2 *= 1000;
1040                 while(start++ < *str) temp2 /= 10;
1041                 *inc += temp2;
1042             }
1043         } else *inc = 0;
1044         *moves = 0; *tc = temp * 1000; *incType = type;
1045         return 0;
1046     }
1047
1048     (*str)++; /* classical time control */
1049     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1050
1051     if(result == 0) {
1052         *moves = temp;
1053         *tc    = temp2 * 1000;
1054         *inc   = 0;
1055         *incType = type;
1056     }
1057     return result;
1058 }
1059
1060 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1061 {   /* [HGM] get time to add from the multi-session time-control string */
1062     int incType, moves=1; /* kludge to force reading of first session */
1063     long time, increment;
1064     char *s = tcString;
1065
1066     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1067     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1068     do {
1069         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1070         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1071         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1072         if(movenr == -1) return time;    /* last move before new session     */
1073         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1074         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1075         if(!moves) return increment;     /* current session is incremental   */
1076         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1077     } while(movenr >= -1);               /* try again for next session       */
1078
1079     return 0; // no new time quota on this move
1080 }
1081
1082 int
1083 ParseTimeControl(tc, ti, mps)
1084      char *tc;
1085      float ti;
1086      int mps;
1087 {
1088   long tc1;
1089   long tc2;
1090   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1091   int min, sec=0;
1092
1093   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1094   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1095       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1096   if(ti > 0) {
1097
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1100     else 
1101       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1102   } else {
1103     if(mps)
1104       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1105     else 
1106       snprintf(buf, MSG_SIZ, ":%s", mytc);
1107   }
1108   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1109   
1110   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1111     return FALSE;
1112   }
1113
1114   if( *tc == '/' ) {
1115     /* Parse second time control */
1116     tc++;
1117
1118     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1119       return FALSE;
1120     }
1121
1122     if( tc2 == 0 ) {
1123       return FALSE;
1124     }
1125
1126     timeControl_2 = tc2 * 1000;
1127   }
1128   else {
1129     timeControl_2 = 0;
1130   }
1131
1132   if( tc1 == 0 ) {
1133     return FALSE;
1134   }
1135
1136   timeControl = tc1 * 1000;
1137
1138   if (ti >= 0) {
1139     timeIncrement = ti * 1000;  /* convert to ms */
1140     movesPerSession = 0;
1141   } else {
1142     timeIncrement = 0;
1143     movesPerSession = mps;
1144   }
1145   return TRUE;
1146 }
1147
1148 void
1149 InitBackEnd2()
1150 {
1151     if (appData.debugMode) {
1152         fprintf(debugFP, "%s\n", programVersion);
1153     }
1154
1155     set_cont_sequence(appData.wrapContSeq);
1156     if (appData.matchGames > 0) {
1157         appData.matchMode = TRUE;
1158     } else if (appData.matchMode) {
1159         appData.matchGames = 1;
1160     }
1161     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1162         appData.matchGames = appData.sameColorGames;
1163     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1164         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1165         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1166     }
1167     Reset(TRUE, FALSE);
1168     if (appData.noChessProgram || first.protocolVersion == 1) {
1169       InitBackEnd3();
1170     } else {
1171       /* kludge: allow timeout for initial "feature" commands */
1172       FreezeUI();
1173       DisplayMessage("", _("Starting chess program"));
1174       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1175     }
1176 }
1177
1178 void
1179 MatchEvent(int mode)
1180 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = mode;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1209         TwoMachinesEvent();
1210 }
1211
1212 void
1213 InitBackEnd3 P((void))
1214 {
1215     GameMode initialMode;
1216     char buf[MSG_SIZ];
1217     int err, len;
1218
1219     InitChessProgram(&first, startedFromSetupPosition);
1220
1221     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1222         free(programVersion);
1223         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1224         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1225     }
1226
1227     if (appData.icsActive) {
1228 #ifdef WIN32
1229         /* [DM] Make a console window if needed [HGM] merged ifs */
1230         ConsoleCreate();
1231 #endif
1232         err = establish();
1233         if (err != 0)
1234           {
1235             if (*appData.icsCommPort != NULLCHAR)
1236               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1237                              appData.icsCommPort);
1238             else
1239               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1240                         appData.icsHost, appData.icsPort);
1241
1242             if( (len > MSG_SIZ) && appData.debugMode )
1243               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1244
1245             DisplayFatalError(buf, err, 1);
1246             return;
1247         }
1248         SetICSMode();
1249         telnetISR =
1250           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1251         fromUserISR =
1252           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1253         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1254             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1255     } else if (appData.noChessProgram) {
1256         SetNCPMode();
1257     } else {
1258         SetGNUMode();
1259     }
1260
1261     if (*appData.cmailGameName != NULLCHAR) {
1262         SetCmailMode();
1263         OpenLoopback(&cmailPR);
1264         cmailISR =
1265           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1266     }
1267
1268     ThawUI();
1269     DisplayMessage("", "");
1270     if (StrCaseCmp(appData.initialMode, "") == 0) {
1271       initialMode = BeginningOfGame;
1272     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1273       initialMode = TwoMachinesPlay;
1274     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1275       initialMode = AnalyzeFile;
1276     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1277       initialMode = AnalyzeMode;
1278     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1279       initialMode = MachinePlaysWhite;
1280     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1281       initialMode = MachinePlaysBlack;
1282     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1283       initialMode = EditGame;
1284     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1285       initialMode = EditPosition;
1286     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1287       initialMode = Training;
1288     } else {
1289       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1290       if( (len > MSG_SIZ) && appData.debugMode )
1291         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1292
1293       DisplayFatalError(buf, 0, 2);
1294       return;
1295     }
1296
1297     if (appData.matchMode) {
1298         MatchEvent(TRUE);
1299     } else if (*appData.cmailGameName != NULLCHAR) {
1300         /* Set up cmail mode */
1301         ReloadCmailMsgEvent(TRUE);
1302     } else {
1303         /* Set up other modes */
1304         if (initialMode == AnalyzeFile) {
1305           if (*appData.loadGameFile == NULLCHAR) {
1306             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1307             return;
1308           }
1309         }
1310         if (*appData.loadGameFile != NULLCHAR) {
1311             (void) LoadGameFromFile(appData.loadGameFile,
1312                                     appData.loadGameIndex,
1313                                     appData.loadGameFile, TRUE);
1314         } else if (*appData.loadPositionFile != NULLCHAR) {
1315             (void) LoadPositionFromFile(appData.loadPositionFile,
1316                                         appData.loadPositionIndex,
1317                                         appData.loadPositionFile);
1318             /* [HGM] try to make self-starting even after FEN load */
1319             /* to allow automatic setup of fairy variants with wtm */
1320             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1321                 gameMode = BeginningOfGame;
1322                 setboardSpoiledMachineBlack = 1;
1323             }
1324             /* [HGM] loadPos: make that every new game uses the setup */
1325             /* from file as long as we do not switch variant          */
1326             if(!blackPlaysFirst) {
1327                 startedFromPositionFile = TRUE;
1328                 CopyBoard(filePosition, boards[0]);
1329             }
1330         }
1331         if (initialMode == AnalyzeMode) {
1332           if (appData.noChessProgram) {
1333             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1334             return;
1335           }
1336           if (appData.icsActive) {
1337             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1338             return;
1339           }
1340           AnalyzeModeEvent();
1341         } else if (initialMode == AnalyzeFile) {
1342           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1343           ShowThinkingEvent();
1344           AnalyzeFileEvent();
1345           AnalysisPeriodicEvent(1);
1346         } else if (initialMode == MachinePlaysWhite) {
1347           if (appData.noChessProgram) {
1348             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1349                               0, 2);
1350             return;
1351           }
1352           if (appData.icsActive) {
1353             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1354                               0, 2);
1355             return;
1356           }
1357           MachineWhiteEvent();
1358         } else if (initialMode == MachinePlaysBlack) {
1359           if (appData.noChessProgram) {
1360             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1361                               0, 2);
1362             return;
1363           }
1364           if (appData.icsActive) {
1365             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1366                               0, 2);
1367             return;
1368           }
1369           MachineBlackEvent();
1370         } else if (initialMode == TwoMachinesPlay) {
1371           if (appData.noChessProgram) {
1372             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1373                               0, 2);
1374             return;
1375           }
1376           if (appData.icsActive) {
1377             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1378                               0, 2);
1379             return;
1380           }
1381           TwoMachinesEvent();
1382         } else if (initialMode == EditGame) {
1383           EditGameEvent();
1384         } else if (initialMode == EditPosition) {
1385           EditPositionEvent();
1386         } else if (initialMode == Training) {
1387           if (*appData.loadGameFile == NULLCHAR) {
1388             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1389             return;
1390           }
1391           TrainingEvent();
1392         }
1393     }
1394 }
1395
1396 /*
1397  * Establish will establish a contact to a remote host.port.
1398  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1399  *  used to talk to the host.
1400  * Returns 0 if okay, error code if not.
1401  */
1402 int
1403 establish()
1404 {
1405     char buf[MSG_SIZ];
1406
1407     if (*appData.icsCommPort != NULLCHAR) {
1408         /* Talk to the host through a serial comm port */
1409         return OpenCommPort(appData.icsCommPort, &icsPR);
1410
1411     } else if (*appData.gateway != NULLCHAR) {
1412         if (*appData.remoteShell == NULLCHAR) {
1413             /* Use the rcmd protocol to run telnet program on a gateway host */
1414             snprintf(buf, sizeof(buf), "%s %s %s",
1415                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1416             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1417
1418         } else {
1419             /* Use the rsh program to run telnet program on a gateway host */
1420             if (*appData.remoteUser == NULLCHAR) {
1421                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1422                         appData.gateway, appData.telnetProgram,
1423                         appData.icsHost, appData.icsPort);
1424             } else {
1425                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1426                         appData.remoteShell, appData.gateway,
1427                         appData.remoteUser, appData.telnetProgram,
1428                         appData.icsHost, appData.icsPort);
1429             }
1430             return StartChildProcess(buf, "", &icsPR);
1431
1432         }
1433     } else if (appData.useTelnet) {
1434         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1435
1436     } else {
1437         /* TCP socket interface differs somewhat between
1438            Unix and NT; handle details in the front end.
1439            */
1440         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1441     }
1442 }
1443
1444 void EscapeExpand(char *p, char *q)
1445 {       // [HGM] initstring: routine to shape up string arguments
1446         while(*p++ = *q++) if(p[-1] == '\\')
1447             switch(*q++) {
1448                 case 'n': p[-1] = '\n'; break;
1449                 case 'r': p[-1] = '\r'; break;
1450                 case 't': p[-1] = '\t'; break;
1451                 case '\\': p[-1] = '\\'; break;
1452                 case 0: *p = 0; return;
1453                 default: p[-1] = q[-1]; break;
1454             }
1455 }
1456
1457 void
1458 show_bytes(fp, buf, count)
1459      FILE *fp;
1460      char *buf;
1461      int count;
1462 {
1463     while (count--) {
1464         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1465             fprintf(fp, "\\%03o", *buf & 0xff);
1466         } else {
1467             putc(*buf, fp);
1468         }
1469         buf++;
1470     }
1471     fflush(fp);
1472 }
1473
1474 /* Returns an errno value */
1475 int
1476 OutputMaybeTelnet(pr, message, count, outError)
1477      ProcRef pr;
1478      char *message;
1479      int count;
1480      int *outError;
1481 {
1482     char buf[8192], *p, *q, *buflim;
1483     int left, newcount, outcount;
1484
1485     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1486         *appData.gateway != NULLCHAR) {
1487         if (appData.debugMode) {
1488             fprintf(debugFP, ">ICS: ");
1489             show_bytes(debugFP, message, count);
1490             fprintf(debugFP, "\n");
1491         }
1492         return OutputToProcess(pr, message, count, outError);
1493     }
1494
1495     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1496     p = message;
1497     q = buf;
1498     left = count;
1499     newcount = 0;
1500     while (left) {
1501         if (q >= buflim) {
1502             if (appData.debugMode) {
1503                 fprintf(debugFP, ">ICS: ");
1504                 show_bytes(debugFP, buf, newcount);
1505                 fprintf(debugFP, "\n");
1506             }
1507             outcount = OutputToProcess(pr, buf, newcount, outError);
1508             if (outcount < newcount) return -1; /* to be sure */
1509             q = buf;
1510             newcount = 0;
1511         }
1512         if (*p == '\n') {
1513             *q++ = '\r';
1514             newcount++;
1515         } else if (((unsigned char) *p) == TN_IAC) {
1516             *q++ = (char) TN_IAC;
1517             newcount ++;
1518         }
1519         *q++ = *p++;
1520         newcount++;
1521         left--;
1522     }
1523     if (appData.debugMode) {
1524         fprintf(debugFP, ">ICS: ");
1525         show_bytes(debugFP, buf, newcount);
1526         fprintf(debugFP, "\n");
1527     }
1528     outcount = OutputToProcess(pr, buf, newcount, outError);
1529     if (outcount < newcount) return -1; /* to be sure */
1530     return count;
1531 }
1532
1533 void
1534 read_from_player(isr, closure, message, count, error)
1535      InputSourceRef isr;
1536      VOIDSTAR closure;
1537      char *message;
1538      int count;
1539      int error;
1540 {
1541     int outError, outCount;
1542     static int gotEof = 0;
1543
1544     /* Pass data read from player on to ICS */
1545     if (count > 0) {
1546         gotEof = 0;
1547         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1548         if (outCount < count) {
1549             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1550         }
1551     } else if (count < 0) {
1552         RemoveInputSource(isr);
1553         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1554     } else if (gotEof++ > 0) {
1555         RemoveInputSource(isr);
1556         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1557     }
1558 }
1559
1560 void
1561 KeepAlive()
1562 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1563     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1564     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1565     SendToICS("date\n");
1566     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567 }
1568
1569 /* added routine for printf style output to ics */
1570 void ics_printf(char *format, ...)
1571 {
1572     char buffer[MSG_SIZ];
1573     va_list args;
1574
1575     va_start(args, format);
1576     vsnprintf(buffer, sizeof(buffer), format, args);
1577     buffer[sizeof(buffer)-1] = '\0';
1578     SendToICS(buffer);
1579     va_end(args);
1580 }
1581
1582 void
1583 SendToICS(s)
1584      char *s;
1585 {
1586     int count, outCount, outError;
1587
1588     if (icsPR == NULL) return;
1589
1590     count = strlen(s);
1591     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1592     if (outCount < count) {
1593         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1594     }
1595 }
1596
1597 /* This is used for sending logon scripts to the ICS. Sending
1598    without a delay causes problems when using timestamp on ICC
1599    (at least on my machine). */
1600 void
1601 SendToICSDelayed(s,msdelay)
1602      char *s;
1603      long msdelay;
1604 {
1605     int count, outCount, outError;
1606
1607     if (icsPR == NULL) return;
1608
1609     count = strlen(s);
1610     if (appData.debugMode) {
1611         fprintf(debugFP, ">ICS: ");
1612         show_bytes(debugFP, s, count);
1613         fprintf(debugFP, "\n");
1614     }
1615     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1616                                       msdelay);
1617     if (outCount < count) {
1618         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1619     }
1620 }
1621
1622
1623 /* Remove all highlighting escape sequences in s
1624    Also deletes any suffix starting with '('
1625    */
1626 char *
1627 StripHighlightAndTitle(s)
1628      char *s;
1629 {
1630     static char retbuf[MSG_SIZ];
1631     char *p = retbuf;
1632
1633     while (*s != NULLCHAR) {
1634         while (*s == '\033') {
1635             while (*s != NULLCHAR && !isalpha(*s)) s++;
1636             if (*s != NULLCHAR) s++;
1637         }
1638         while (*s != NULLCHAR && *s != '\033') {
1639             if (*s == '(' || *s == '[') {
1640                 *p = NULLCHAR;
1641                 return retbuf;
1642             }
1643             *p++ = *s++;
1644         }
1645     }
1646     *p = NULLCHAR;
1647     return retbuf;
1648 }
1649
1650 /* Remove all highlighting escape sequences in s */
1651 char *
1652 StripHighlight(s)
1653      char *s;
1654 {
1655     static char retbuf[MSG_SIZ];
1656     char *p = retbuf;
1657
1658     while (*s != NULLCHAR) {
1659         while (*s == '\033') {
1660             while (*s != NULLCHAR && !isalpha(*s)) s++;
1661             if (*s != NULLCHAR) s++;
1662         }
1663         while (*s != NULLCHAR && *s != '\033') {
1664             *p++ = *s++;
1665         }
1666     }
1667     *p = NULLCHAR;
1668     return retbuf;
1669 }
1670
1671 char *variantNames[] = VARIANT_NAMES;
1672 char *
1673 VariantName(v)
1674      VariantClass v;
1675 {
1676     return variantNames[v];
1677 }
1678
1679
1680 /* Identify a variant from the strings the chess servers use or the
1681    PGN Variant tag names we use. */
1682 VariantClass
1683 StringToVariant(e)
1684      char *e;
1685 {
1686     char *p;
1687     int wnum = -1;
1688     VariantClass v = VariantNormal;
1689     int i, found = FALSE;
1690     char buf[MSG_SIZ];
1691     int len;
1692
1693     if (!e) return v;
1694
1695     /* [HGM] skip over optional board-size prefixes */
1696     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1697         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1698         while( *e++ != '_');
1699     }
1700
1701     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1702         v = VariantNormal;
1703         found = TRUE;
1704     } else
1705     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1706       if (StrCaseStr(e, variantNames[i])) {
1707         v = (VariantClass) i;
1708         found = TRUE;
1709         break;
1710       }
1711     }
1712
1713     if (!found) {
1714       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1715           || StrCaseStr(e, "wild/fr")
1716           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1717         v = VariantFischeRandom;
1718       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1719                  (i = 1, p = StrCaseStr(e, "w"))) {
1720         p += i;
1721         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1722         if (isdigit(*p)) {
1723           wnum = atoi(p);
1724         } else {
1725           wnum = -1;
1726         }
1727         switch (wnum) {
1728         case 0: /* FICS only, actually */
1729         case 1:
1730           /* Castling legal even if K starts on d-file */
1731           v = VariantWildCastle;
1732           break;
1733         case 2:
1734         case 3:
1735         case 4:
1736           /* Castling illegal even if K & R happen to start in
1737              normal positions. */
1738           v = VariantNoCastle;
1739           break;
1740         case 5:
1741         case 7:
1742         case 8:
1743         case 10:
1744         case 11:
1745         case 12:
1746         case 13:
1747         case 14:
1748         case 15:
1749         case 18:
1750         case 19:
1751           /* Castling legal iff K & R start in normal positions */
1752           v = VariantNormal;
1753           break;
1754         case 6:
1755         case 20:
1756         case 21:
1757           /* Special wilds for position setup; unclear what to do here */
1758           v = VariantLoadable;
1759           break;
1760         case 9:
1761           /* Bizarre ICC game */
1762           v = VariantTwoKings;
1763           break;
1764         case 16:
1765           v = VariantKriegspiel;
1766           break;
1767         case 17:
1768           v = VariantLosers;
1769           break;
1770         case 22:
1771           v = VariantFischeRandom;
1772           break;
1773         case 23:
1774           v = VariantCrazyhouse;
1775           break;
1776         case 24:
1777           v = VariantBughouse;
1778           break;
1779         case 25:
1780           v = Variant3Check;
1781           break;
1782         case 26:
1783           /* Not quite the same as FICS suicide! */
1784           v = VariantGiveaway;
1785           break;
1786         case 27:
1787           v = VariantAtomic;
1788           break;
1789         case 28:
1790           v = VariantShatranj;
1791           break;
1792
1793         /* Temporary names for future ICC types.  The name *will* change in
1794            the next xboard/WinBoard release after ICC defines it. */
1795         case 29:
1796           v = Variant29;
1797           break;
1798         case 30:
1799           v = Variant30;
1800           break;
1801         case 31:
1802           v = Variant31;
1803           break;
1804         case 32:
1805           v = Variant32;
1806           break;
1807         case 33:
1808           v = Variant33;
1809           break;
1810         case 34:
1811           v = Variant34;
1812           break;
1813         case 35:
1814           v = Variant35;
1815           break;
1816         case 36:
1817           v = Variant36;
1818           break;
1819         case 37:
1820           v = VariantShogi;
1821           break;
1822         case 38:
1823           v = VariantXiangqi;
1824           break;
1825         case 39:
1826           v = VariantCourier;
1827           break;
1828         case 40:
1829           v = VariantGothic;
1830           break;
1831         case 41:
1832           v = VariantCapablanca;
1833           break;
1834         case 42:
1835           v = VariantKnightmate;
1836           break;
1837         case 43:
1838           v = VariantFairy;
1839           break;
1840         case 44:
1841           v = VariantCylinder;
1842           break;
1843         case 45:
1844           v = VariantFalcon;
1845           break;
1846         case 46:
1847           v = VariantCapaRandom;
1848           break;
1849         case 47:
1850           v = VariantBerolina;
1851           break;
1852         case 48:
1853           v = VariantJanus;
1854           break;
1855         case 49:
1856           v = VariantSuper;
1857           break;
1858         case 50:
1859           v = VariantGreat;
1860           break;
1861         case -1:
1862           /* Found "wild" or "w" in the string but no number;
1863              must assume it's normal chess. */
1864           v = VariantNormal;
1865           break;
1866         default:
1867           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1868           if( (len > MSG_SIZ) && appData.debugMode )
1869             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1870
1871           DisplayError(buf, 0);
1872           v = VariantUnknown;
1873           break;
1874         }
1875       }
1876     }
1877     if (appData.debugMode) {
1878       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1879               e, wnum, VariantName(v));
1880     }
1881     return v;
1882 }
1883
1884 static int leftover_start = 0, leftover_len = 0;
1885 char star_match[STAR_MATCH_N][MSG_SIZ];
1886
1887 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1888    advance *index beyond it, and set leftover_start to the new value of
1889    *index; else return FALSE.  If pattern contains the character '*', it
1890    matches any sequence of characters not containing '\r', '\n', or the
1891    character following the '*' (if any), and the matched sequence(s) are
1892    copied into star_match.
1893    */
1894 int
1895 looking_at(buf, index, pattern)
1896      char *buf;
1897      int *index;
1898      char *pattern;
1899 {
1900     char *bufp = &buf[*index], *patternp = pattern;
1901     int star_count = 0;
1902     char *matchp = star_match[0];
1903
1904     for (;;) {
1905         if (*patternp == NULLCHAR) {
1906             *index = leftover_start = bufp - buf;
1907             *matchp = NULLCHAR;
1908             return TRUE;
1909         }
1910         if (*bufp == NULLCHAR) return FALSE;
1911         if (*patternp == '*') {
1912             if (*bufp == *(patternp + 1)) {
1913                 *matchp = NULLCHAR;
1914                 matchp = star_match[++star_count];
1915                 patternp += 2;
1916                 bufp++;
1917                 continue;
1918             } else if (*bufp == '\n' || *bufp == '\r') {
1919                 patternp++;
1920                 if (*patternp == NULLCHAR)
1921                   continue;
1922                 else
1923                   return FALSE;
1924             } else {
1925                 *matchp++ = *bufp++;
1926                 continue;
1927             }
1928         }
1929         if (*patternp != *bufp) return FALSE;
1930         patternp++;
1931         bufp++;
1932     }
1933 }
1934
1935 void
1936 SendToPlayer(data, length)
1937      char *data;
1938      int length;
1939 {
1940     int error, outCount;
1941     outCount = OutputToProcess(NoProc, data, length, &error);
1942     if (outCount < length) {
1943         DisplayFatalError(_("Error writing to display"), error, 1);
1944     }
1945 }
1946
1947 void
1948 PackHolding(packed, holding)
1949      char packed[];
1950      char *holding;
1951 {
1952     char *p = holding;
1953     char *q = packed;
1954     int runlength = 0;
1955     int curr = 9999;
1956     do {
1957         if (*p == curr) {
1958             runlength++;
1959         } else {
1960             switch (runlength) {
1961               case 0:
1962                 break;
1963               case 1:
1964                 *q++ = curr;
1965                 break;
1966               case 2:
1967                 *q++ = curr;
1968                 *q++ = curr;
1969                 break;
1970               default:
1971                 sprintf(q, "%d", runlength);
1972                 while (*q) q++;
1973                 *q++ = curr;
1974                 break;
1975             }
1976             runlength = 1;
1977             curr = *p;
1978         }
1979     } while (*p++);
1980     *q = NULLCHAR;
1981 }
1982
1983 /* Telnet protocol requests from the front end */
1984 void
1985 TelnetRequest(ddww, option)
1986      unsigned char ddww, option;
1987 {
1988     unsigned char msg[3];
1989     int outCount, outError;
1990
1991     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1992
1993     if (appData.debugMode) {
1994         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1995         switch (ddww) {
1996           case TN_DO:
1997             ddwwStr = "DO";
1998             break;
1999           case TN_DONT:
2000             ddwwStr = "DONT";
2001             break;
2002           case TN_WILL:
2003             ddwwStr = "WILL";
2004             break;
2005           case TN_WONT:
2006             ddwwStr = "WONT";
2007             break;
2008           default:
2009             ddwwStr = buf1;
2010             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2011             break;
2012         }
2013         switch (option) {
2014           case TN_ECHO:
2015             optionStr = "ECHO";
2016             break;
2017           default:
2018             optionStr = buf2;
2019             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2020             break;
2021         }
2022         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2023     }
2024     msg[0] = TN_IAC;
2025     msg[1] = ddww;
2026     msg[2] = option;
2027     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2028     if (outCount < 3) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033 void
2034 DoEcho()
2035 {
2036     if (!appData.icsActive) return;
2037     TelnetRequest(TN_DO, TN_ECHO);
2038 }
2039
2040 void
2041 DontEcho()
2042 {
2043     if (!appData.icsActive) return;
2044     TelnetRequest(TN_DONT, TN_ECHO);
2045 }
2046
2047 void
2048 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2049 {
2050     /* put the holdings sent to us by the server on the board holdings area */
2051     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2052     char p;
2053     ChessSquare piece;
2054
2055     if(gameInfo.holdingsWidth < 2)  return;
2056     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2057         return; // prevent overwriting by pre-board holdings
2058
2059     if( (int)lowestPiece >= BlackPawn ) {
2060         holdingsColumn = 0;
2061         countsColumn = 1;
2062         holdingsStartRow = BOARD_HEIGHT-1;
2063         direction = -1;
2064     } else {
2065         holdingsColumn = BOARD_WIDTH-1;
2066         countsColumn = BOARD_WIDTH-2;
2067         holdingsStartRow = 0;
2068         direction = 1;
2069     }
2070
2071     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2072         board[i][holdingsColumn] = EmptySquare;
2073         board[i][countsColumn]   = (ChessSquare) 0;
2074     }
2075     while( (p=*holdings++) != NULLCHAR ) {
2076         piece = CharToPiece( ToUpper(p) );
2077         if(piece == EmptySquare) continue;
2078         /*j = (int) piece - (int) WhitePawn;*/
2079         j = PieceToNumber(piece);
2080         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2081         if(j < 0) continue;               /* should not happen */
2082         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2083         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2084         board[holdingsStartRow+j*direction][countsColumn]++;
2085     }
2086 }
2087
2088
2089 void
2090 VariantSwitch(Board board, VariantClass newVariant)
2091 {
2092    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2093    static Board oldBoard;
2094
2095    startedFromPositionFile = FALSE;
2096    if(gameInfo.variant == newVariant) return;
2097
2098    /* [HGM] This routine is called each time an assignment is made to
2099     * gameInfo.variant during a game, to make sure the board sizes
2100     * are set to match the new variant. If that means adding or deleting
2101     * holdings, we shift the playing board accordingly
2102     * This kludge is needed because in ICS observe mode, we get boards
2103     * of an ongoing game without knowing the variant, and learn about the
2104     * latter only later. This can be because of the move list we requested,
2105     * in which case the game history is refilled from the beginning anyway,
2106     * but also when receiving holdings of a crazyhouse game. In the latter
2107     * case we want to add those holdings to the already received position.
2108     */
2109
2110
2111    if (appData.debugMode) {
2112      fprintf(debugFP, "Switch board from %s to %s\n",
2113              VariantName(gameInfo.variant), VariantName(newVariant));
2114      setbuf(debugFP, NULL);
2115    }
2116    shuffleOpenings = 0;       /* [HGM] shuffle */
2117    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2118    switch(newVariant)
2119      {
2120      case VariantShogi:
2121        newWidth = 9;  newHeight = 9;
2122        gameInfo.holdingsSize = 7;
2123      case VariantBughouse:
2124      case VariantCrazyhouse:
2125        newHoldingsWidth = 2; break;
2126      case VariantGreat:
2127        newWidth = 10;
2128      case VariantSuper:
2129        newHoldingsWidth = 2;
2130        gameInfo.holdingsSize = 8;
2131        break;
2132      case VariantGothic:
2133      case VariantCapablanca:
2134      case VariantCapaRandom:
2135        newWidth = 10;
2136      default:
2137        newHoldingsWidth = gameInfo.holdingsSize = 0;
2138      };
2139
2140    if(newWidth  != gameInfo.boardWidth  ||
2141       newHeight != gameInfo.boardHeight ||
2142       newHoldingsWidth != gameInfo.holdingsWidth ) {
2143
2144      /* shift position to new playing area, if needed */
2145      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2146        for(i=0; i<BOARD_HEIGHT; i++)
2147          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2148            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2149              board[i][j];
2150        for(i=0; i<newHeight; i++) {
2151          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2152          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2153        }
2154      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2155        for(i=0; i<BOARD_HEIGHT; i++)
2156          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2157            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2158              board[i][j];
2159      }
2160      gameInfo.boardWidth  = newWidth;
2161      gameInfo.boardHeight = newHeight;
2162      gameInfo.holdingsWidth = newHoldingsWidth;
2163      gameInfo.variant = newVariant;
2164      InitDrawingSizes(-2, 0);
2165    } else gameInfo.variant = newVariant;
2166    CopyBoard(oldBoard, board);   // remember correctly formatted board
2167      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2168    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2169 }
2170
2171 static int loggedOn = FALSE;
2172
2173 /*-- Game start info cache: --*/
2174 int gs_gamenum;
2175 char gs_kind[MSG_SIZ];
2176 static char player1Name[128] = "";
2177 static char player2Name[128] = "";
2178 static char cont_seq[] = "\n\\   ";
2179 static int player1Rating = -1;
2180 static int player2Rating = -1;
2181 /*----------------------------*/
2182
2183 ColorClass curColor = ColorNormal;
2184 int suppressKibitz = 0;
2185
2186 // [HGM] seekgraph
2187 Boolean soughtPending = FALSE;
2188 Boolean seekGraphUp;
2189 #define MAX_SEEK_ADS 200
2190 #define SQUARE 0x80
2191 char *seekAdList[MAX_SEEK_ADS];
2192 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2193 float tcList[MAX_SEEK_ADS];
2194 char colorList[MAX_SEEK_ADS];
2195 int nrOfSeekAds = 0;
2196 int minRating = 1010, maxRating = 2800;
2197 int hMargin = 10, vMargin = 20, h, w;
2198 extern int squareSize, lineGap;
2199
2200 void
2201 PlotSeekAd(int i)
2202 {
2203         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2204         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2205         if(r < minRating+100 && r >=0 ) r = minRating+100;
2206         if(r > maxRating) r = maxRating;
2207         if(tc < 1.) tc = 1.;
2208         if(tc > 95.) tc = 95.;
2209         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2210         y = ((double)r - minRating)/(maxRating - minRating)
2211             * (h-vMargin-squareSize/8-1) + vMargin;
2212         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2213         if(strstr(seekAdList[i], " u ")) color = 1;
2214         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2215            !strstr(seekAdList[i], "bullet") &&
2216            !strstr(seekAdList[i], "blitz") &&
2217            !strstr(seekAdList[i], "standard") ) color = 2;
2218         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2219         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2220 }
2221
2222 void
2223 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2224 {
2225         char buf[MSG_SIZ], *ext = "";
2226         VariantClass v = StringToVariant(type);
2227         if(strstr(type, "wild")) {
2228             ext = type + 4; // append wild number
2229             if(v == VariantFischeRandom) type = "chess960"; else
2230             if(v == VariantLoadable) type = "setup"; else
2231             type = VariantName(v);
2232         }
2233         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2234         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2235             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2236             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2237             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2238             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2239             seekNrList[nrOfSeekAds] = nr;
2240             zList[nrOfSeekAds] = 0;
2241             seekAdList[nrOfSeekAds++] = StrSave(buf);
2242             if(plot) PlotSeekAd(nrOfSeekAds-1);
2243         }
2244 }
2245
2246 void
2247 EraseSeekDot(int i)
2248 {
2249     int x = xList[i], y = yList[i], d=squareSize/4, k;
2250     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2251     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2252     // now replot every dot that overlapped
2253     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2254         int xx = xList[k], yy = yList[k];
2255         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2256             DrawSeekDot(xx, yy, colorList[k]);
2257     }
2258 }
2259
2260 void
2261 RemoveSeekAd(int nr)
2262 {
2263         int i;
2264         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2265             EraseSeekDot(i);
2266             if(seekAdList[i]) free(seekAdList[i]);
2267             seekAdList[i] = seekAdList[--nrOfSeekAds];
2268             seekNrList[i] = seekNrList[nrOfSeekAds];
2269             ratingList[i] = ratingList[nrOfSeekAds];
2270             colorList[i]  = colorList[nrOfSeekAds];
2271             tcList[i] = tcList[nrOfSeekAds];
2272             xList[i]  = xList[nrOfSeekAds];
2273             yList[i]  = yList[nrOfSeekAds];
2274             zList[i]  = zList[nrOfSeekAds];
2275             seekAdList[nrOfSeekAds] = NULL;
2276             break;
2277         }
2278 }
2279
2280 Boolean
2281 MatchSoughtLine(char *line)
2282 {
2283     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2284     int nr, base, inc, u=0; char dummy;
2285
2286     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2287        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2288        (u=1) &&
2289        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2290         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2291         // match: compact and save the line
2292         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2293         return TRUE;
2294     }
2295     return FALSE;
2296 }
2297
2298 int
2299 DrawSeekGraph()
2300 {
2301     int i;
2302     if(!seekGraphUp) return FALSE;
2303     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2304     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2305
2306     DrawSeekBackground(0, 0, w, h);
2307     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2308     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2309     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2310         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2311         yy = h-1-yy;
2312         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2313         if(i%500 == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2317         }
2318     }
2319     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2320     for(i=1; i<100; i+=(i<10?1:5)) {
2321         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2322         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2323         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2324             char buf[MSG_SIZ];
2325             snprintf(buf, MSG_SIZ, "%d", i);
2326             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2327         }
2328     }
2329     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2330     return TRUE;
2331 }
2332
2333 int SeekGraphClick(ClickType click, int x, int y, int moving)
2334 {
2335     static int lastDown = 0, displayed = 0, lastSecond;
2336     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2337         if(click == Release || moving) return FALSE;
2338         nrOfSeekAds = 0;
2339         soughtPending = TRUE;
2340         SendToICS(ics_prefix);
2341         SendToICS("sought\n"); // should this be "sought all"?
2342     } else { // issue challenge based on clicked ad
2343         int dist = 10000; int i, closest = 0, second = 0;
2344         for(i=0; i<nrOfSeekAds; i++) {
2345             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2346             if(d < dist) { dist = d; closest = i; }
2347             second += (d - zList[i] < 120); // count in-range ads
2348             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2349         }
2350         if(dist < 120) {
2351             char buf[MSG_SIZ];
2352             second = (second > 1);
2353             if(displayed != closest || second != lastSecond) {
2354                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2355                 lastSecond = second; displayed = closest;
2356             }
2357             if(click == Press) {
2358                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2359                 lastDown = closest;
2360                 return TRUE;
2361             } // on press 'hit', only show info
2362             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2363             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2364             SendToICS(ics_prefix);
2365             SendToICS(buf);
2366             return TRUE; // let incoming board of started game pop down the graph
2367         } else if(click == Release) { // release 'miss' is ignored
2368             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2369             if(moving == 2) { // right up-click
2370                 nrOfSeekAds = 0; // refresh graph
2371                 soughtPending = TRUE;
2372                 SendToICS(ics_prefix);
2373                 SendToICS("sought\n"); // should this be "sought all"?
2374             }
2375             return TRUE;
2376         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2377         // press miss or release hit 'pop down' seek graph
2378         seekGraphUp = FALSE;
2379         DrawPosition(TRUE, NULL);
2380     }
2381     return TRUE;
2382 }
2383
2384 void
2385 read_from_ics(isr, closure, data, count, error)
2386      InputSourceRef isr;
2387      VOIDSTAR closure;
2388      char *data;
2389      int count;
2390      int error;
2391 {
2392 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2393 #define STARTED_NONE 0
2394 #define STARTED_MOVES 1
2395 #define STARTED_BOARD 2
2396 #define STARTED_OBSERVE 3
2397 #define STARTED_HOLDINGS 4
2398 #define STARTED_CHATTER 5
2399 #define STARTED_COMMENT 6
2400 #define STARTED_MOVES_NOHIDE 7
2401
2402     static int started = STARTED_NONE;
2403     static char parse[20000];
2404     static int parse_pos = 0;
2405     static char buf[BUF_SIZE + 1];
2406     static int firstTime = TRUE, intfSet = FALSE;
2407     static ColorClass prevColor = ColorNormal;
2408     static int savingComment = FALSE;
2409     static int cmatch = 0; // continuation sequence match
2410     char *bp;
2411     char str[MSG_SIZ];
2412     int i, oldi;
2413     int buf_len;
2414     int next_out;
2415     int tkind;
2416     int backup;    /* [DM] For zippy color lines */
2417     char *p;
2418     char talker[MSG_SIZ]; // [HGM] chat
2419     int channel;
2420
2421     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2422
2423     if (appData.debugMode) {
2424       if (!error) {
2425         fprintf(debugFP, "<ICS: ");
2426         show_bytes(debugFP, data, count);
2427         fprintf(debugFP, "\n");
2428       }
2429     }
2430
2431     if (appData.debugMode) { int f = forwardMostMove;
2432         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2433                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2434                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2435     }
2436     if (count > 0) {
2437         /* If last read ended with a partial line that we couldn't parse,
2438            prepend it to the new read and try again. */
2439         if (leftover_len > 0) {
2440             for (i=0; i<leftover_len; i++)
2441               buf[i] = buf[leftover_start + i];
2442         }
2443
2444     /* copy new characters into the buffer */
2445     bp = buf + leftover_len;
2446     buf_len=leftover_len;
2447     for (i=0; i<count; i++)
2448     {
2449         // ignore these
2450         if (data[i] == '\r')
2451             continue;
2452
2453         // join lines split by ICS?
2454         if (!appData.noJoin)
2455         {
2456             /*
2457                 Joining just consists of finding matches against the
2458                 continuation sequence, and discarding that sequence
2459                 if found instead of copying it.  So, until a match
2460                 fails, there's nothing to do since it might be the
2461                 complete sequence, and thus, something we don't want
2462                 copied.
2463             */
2464             if (data[i] == cont_seq[cmatch])
2465             {
2466                 cmatch++;
2467                 if (cmatch == strlen(cont_seq))
2468                 {
2469                     cmatch = 0; // complete match.  just reset the counter
2470
2471                     /*
2472                         it's possible for the ICS to not include the space
2473                         at the end of the last word, making our [correct]
2474                         join operation fuse two separate words.  the server
2475                         does this when the space occurs at the width setting.
2476                     */
2477                     if (!buf_len || buf[buf_len-1] != ' ')
2478                     {
2479                         *bp++ = ' ';
2480                         buf_len++;
2481                     }
2482                 }
2483                 continue;
2484             }
2485             else if (cmatch)
2486             {
2487                 /*
2488                     match failed, so we have to copy what matched before
2489                     falling through and copying this character.  In reality,
2490                     this will only ever be just the newline character, but
2491                     it doesn't hurt to be precise.
2492                 */
2493                 strncpy(bp, cont_seq, cmatch);
2494                 bp += cmatch;
2495                 buf_len += cmatch;
2496                 cmatch = 0;
2497             }
2498         }
2499
2500         // copy this char
2501         *bp++ = data[i];
2502         buf_len++;
2503     }
2504
2505         buf[buf_len] = NULLCHAR;
2506 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2507         next_out = 0;
2508         leftover_start = 0;
2509
2510         i = 0;
2511         while (i < buf_len) {
2512             /* Deal with part of the TELNET option negotiation
2513                protocol.  We refuse to do anything beyond the
2514                defaults, except that we allow the WILL ECHO option,
2515                which ICS uses to turn off password echoing when we are
2516                directly connected to it.  We reject this option
2517                if localLineEditing mode is on (always on in xboard)
2518                and we are talking to port 23, which might be a real
2519                telnet server that will try to keep WILL ECHO on permanently.
2520              */
2521             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2522                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2523                 unsigned char option;
2524                 oldi = i;
2525                 switch ((unsigned char) buf[++i]) {
2526                   case TN_WILL:
2527                     if (appData.debugMode)
2528                       fprintf(debugFP, "\n<WILL ");
2529                     switch (option = (unsigned char) buf[++i]) {
2530                       case TN_ECHO:
2531                         if (appData.debugMode)
2532                           fprintf(debugFP, "ECHO ");
2533                         /* Reply only if this is a change, according
2534                            to the protocol rules. */
2535                         if (remoteEchoOption) break;
2536                         if (appData.localLineEditing &&
2537                             atoi(appData.icsPort) == TN_PORT) {
2538                             TelnetRequest(TN_DONT, TN_ECHO);
2539                         } else {
2540                             EchoOff();
2541                             TelnetRequest(TN_DO, TN_ECHO);
2542                             remoteEchoOption = TRUE;
2543                         }
2544                         break;
2545                       default:
2546                         if (appData.debugMode)
2547                           fprintf(debugFP, "%d ", option);
2548                         /* Whatever this is, we don't want it. */
2549                         TelnetRequest(TN_DONT, option);
2550                         break;
2551                     }
2552                     break;
2553                   case TN_WONT:
2554                     if (appData.debugMode)
2555                       fprintf(debugFP, "\n<WONT ");
2556                     switch (option = (unsigned char) buf[++i]) {
2557                       case TN_ECHO:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "ECHO ");
2560                         /* Reply only if this is a change, according
2561                            to the protocol rules. */
2562                         if (!remoteEchoOption) break;
2563                         EchoOn();
2564                         TelnetRequest(TN_DONT, TN_ECHO);
2565                         remoteEchoOption = FALSE;
2566                         break;
2567                       default:
2568                         if (appData.debugMode)
2569                           fprintf(debugFP, "%d ", (unsigned char) option);
2570                         /* Whatever this is, it must already be turned
2571                            off, because we never agree to turn on
2572                            anything non-default, so according to the
2573                            protocol rules, we don't reply. */
2574                         break;
2575                     }
2576                     break;
2577                   case TN_DO:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<DO ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       default:
2582                         /* Whatever this is, we refuse to do it. */
2583                         if (appData.debugMode)
2584                           fprintf(debugFP, "%d ", option);
2585                         TelnetRequest(TN_WONT, option);
2586                         break;
2587                     }
2588                     break;
2589                   case TN_DONT:
2590                     if (appData.debugMode)
2591                       fprintf(debugFP, "\n<DONT ");
2592                     switch (option = (unsigned char) buf[++i]) {
2593                       default:
2594                         if (appData.debugMode)
2595                           fprintf(debugFP, "%d ", option);
2596                         /* Whatever this is, we are already not doing
2597                            it, because we never agree to do anything
2598                            non-default, so according to the protocol
2599                            rules, we don't reply. */
2600                         break;
2601                     }
2602                     break;
2603                   case TN_IAC:
2604                     if (appData.debugMode)
2605                       fprintf(debugFP, "\n<IAC ");
2606                     /* Doubled IAC; pass it through */
2607                     i--;
2608                     break;
2609                   default:
2610                     if (appData.debugMode)
2611                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2612                     /* Drop all other telnet commands on the floor */
2613                     break;
2614                 }
2615                 if (oldi > next_out)
2616                   SendToPlayer(&buf[next_out], oldi - next_out);
2617                 if (++i > next_out)
2618                   next_out = i;
2619                 continue;
2620             }
2621
2622             /* OK, this at least will *usually* work */
2623             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2624                 loggedOn = TRUE;
2625             }
2626
2627             if (loggedOn && !intfSet) {
2628                 if (ics_type == ICS_ICC) {
2629                   snprintf(str, MSG_SIZ,
2630                           "/set-quietly interface %s\n/set-quietly style 12\n",
2631                           programVersion);
2632                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2634                 } else if (ics_type == ICS_CHESSNET) {
2635                   snprintf(str, MSG_SIZ, "/style 12\n");
2636                 } else {
2637                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2638                   strcat(str, programVersion);
2639                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2640                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2641                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2642 #ifdef WIN32
2643                   strcat(str, "$iset nohighlight 1\n");
2644 #endif
2645                   strcat(str, "$iset lock 1\n$style 12\n");
2646                 }
2647                 SendToICS(str);
2648                 NotifyFrontendLogin();
2649                 intfSet = TRUE;
2650             }
2651
2652             if (started == STARTED_COMMENT) {
2653                 /* Accumulate characters in comment */
2654                 parse[parse_pos++] = buf[i];
2655                 if (buf[i] == '\n') {
2656                     parse[parse_pos] = NULLCHAR;
2657                     if(chattingPartner>=0) {
2658                         char mess[MSG_SIZ];
2659                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2660                         OutputChatMessage(chattingPartner, mess);
2661                         chattingPartner = -1;
2662                         next_out = i+1; // [HGM] suppress printing in ICS window
2663                     } else
2664                     if(!suppressKibitz) // [HGM] kibitz
2665                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2666                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2667                         int nrDigit = 0, nrAlph = 0, j;
2668                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2669                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2670                         parse[parse_pos] = NULLCHAR;
2671                         // try to be smart: if it does not look like search info, it should go to
2672                         // ICS interaction window after all, not to engine-output window.
2673                         for(j=0; j<parse_pos; j++) { // count letters and digits
2674                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2675                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2676                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2677                         }
2678                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2679                             int depth=0; float score;
2680                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2681                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2682                                 pvInfoList[forwardMostMove-1].depth = depth;
2683                                 pvInfoList[forwardMostMove-1].score = 100*score;
2684                             }
2685                             OutputKibitz(suppressKibitz, parse);
2686                         } else {
2687                             char tmp[MSG_SIZ];
2688                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2689                             SendToPlayer(tmp, strlen(tmp));
2690                         }
2691                         next_out = i+1; // [HGM] suppress printing in ICS window
2692                     }
2693                     started = STARTED_NONE;
2694                 } else {
2695                     /* Don't match patterns against characters in comment */
2696                     i++;
2697                     continue;
2698                 }
2699             }
2700             if (started == STARTED_CHATTER) {
2701                 if (buf[i] != '\n') {
2702                     /* Don't match patterns against characters in chatter */
2703                     i++;
2704                     continue;
2705                 }
2706                 started = STARTED_NONE;
2707                 if(suppressKibitz) next_out = i+1;
2708             }
2709
2710             /* Kludge to deal with rcmd protocol */
2711             if (firstTime && looking_at(buf, &i, "\001*")) {
2712                 DisplayFatalError(&buf[1], 0, 1);
2713                 continue;
2714             } else {
2715                 firstTime = FALSE;
2716             }
2717
2718             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2719                 ics_type = ICS_ICC;
2720                 ics_prefix = "/";
2721                 if (appData.debugMode)
2722                   fprintf(debugFP, "ics_type %d\n", ics_type);
2723                 continue;
2724             }
2725             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2726                 ics_type = ICS_FICS;
2727                 ics_prefix = "$";
2728                 if (appData.debugMode)
2729                   fprintf(debugFP, "ics_type %d\n", ics_type);
2730                 continue;
2731             }
2732             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2733                 ics_type = ICS_CHESSNET;
2734                 ics_prefix = "/";
2735                 if (appData.debugMode)
2736                   fprintf(debugFP, "ics_type %d\n", ics_type);
2737                 continue;
2738             }
2739
2740             if (!loggedOn &&
2741                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2742                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2743                  looking_at(buf, &i, "will be \"*\""))) {
2744               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2745               continue;
2746             }
2747
2748             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2749               char buf[MSG_SIZ];
2750               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2751               DisplayIcsInteractionTitle(buf);
2752               have_set_title = TRUE;
2753             }
2754
2755             /* skip finger notes */
2756             if (started == STARTED_NONE &&
2757                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2758                  (buf[i] == '1' && buf[i+1] == '0')) &&
2759                 buf[i+2] == ':' && buf[i+3] == ' ') {
2760               started = STARTED_CHATTER;
2761               i += 3;
2762               continue;
2763             }
2764
2765             oldi = i;
2766             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2767             if(appData.seekGraph) {
2768                 if(soughtPending && MatchSoughtLine(buf+i)) {
2769                     i = strstr(buf+i, "rated") - buf;
2770                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2771                     next_out = leftover_start = i;
2772                     started = STARTED_CHATTER;
2773                     suppressKibitz = TRUE;
2774                     continue;
2775                 }
2776                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2777                         && looking_at(buf, &i, "* ads displayed")) {
2778                     soughtPending = FALSE;
2779                     seekGraphUp = TRUE;
2780                     DrawSeekGraph();
2781                     continue;
2782                 }
2783                 if(appData.autoRefresh) {
2784                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2785                         int s = (ics_type == ICS_ICC); // ICC format differs
2786                         if(seekGraphUp)
2787                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2788                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2789                         looking_at(buf, &i, "*% "); // eat prompt
2790                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2791                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2792                         next_out = i; // suppress
2793                         continue;
2794                     }
2795                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2796                         char *p = star_match[0];
2797                         while(*p) {
2798                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2799                             while(*p && *p++ != ' '); // next
2800                         }
2801                         looking_at(buf, &i, "*% "); // eat prompt
2802                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2803                         next_out = i;
2804                         continue;
2805                     }
2806                 }
2807             }
2808
2809             /* skip formula vars */
2810             if (started == STARTED_NONE &&
2811                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2812               started = STARTED_CHATTER;
2813               i += 3;
2814               continue;
2815             }
2816
2817             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2818             if (appData.autoKibitz && started == STARTED_NONE &&
2819                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2820                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2821                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2822                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2823                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2824                         suppressKibitz = TRUE;
2825                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2826                         next_out = i;
2827                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2828                                 && (gameMode == IcsPlayingWhite)) ||
2829                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2830                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2831                             started = STARTED_CHATTER; // own kibitz we simply discard
2832                         else {
2833                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2834                             parse_pos = 0; parse[0] = NULLCHAR;
2835                             savingComment = TRUE;
2836                             suppressKibitz = gameMode != IcsObserving ? 2 :
2837                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2838                         }
2839                         continue;
2840                 } else
2841                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2842                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2843                          && atoi(star_match[0])) {
2844                     // suppress the acknowledgements of our own autoKibitz
2845                     char *p;
2846                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2847                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2848                     SendToPlayer(star_match[0], strlen(star_match[0]));
2849                     if(looking_at(buf, &i, "*% ")) // eat prompt
2850                         suppressKibitz = FALSE;
2851                     next_out = i;
2852                     continue;
2853                 }
2854             } // [HGM] kibitz: end of patch
2855
2856             // [HGM] chat: intercept tells by users for which we have an open chat window
2857             channel = -1;
2858             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2859                                            looking_at(buf, &i, "* whispers:") ||
2860                                            looking_at(buf, &i, "* kibitzes:") ||
2861                                            looking_at(buf, &i, "* shouts:") ||
2862                                            looking_at(buf, &i, "* c-shouts:") ||
2863                                            looking_at(buf, &i, "--> * ") ||
2864                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2865                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2866                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2867                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2868                 int p;
2869                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2870                 chattingPartner = -1;
2871
2872                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2873                 for(p=0; p<MAX_CHAT; p++) {
2874                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2875                     talker[0] = '['; strcat(talker, "] ");
2876                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2877                     chattingPartner = p; break;
2878                     }
2879                 } else
2880                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2881                 for(p=0; p<MAX_CHAT; p++) {
2882                     if(!strcmp("kibitzes", chatPartner[p])) {
2883                         talker[0] = '['; strcat(talker, "] ");
2884                         chattingPartner = p; break;
2885                     }
2886                 } else
2887                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2888                 for(p=0; p<MAX_CHAT; p++) {
2889                     if(!strcmp("whispers", chatPartner[p])) {
2890                         talker[0] = '['; strcat(talker, "] ");
2891                         chattingPartner = p; break;
2892                     }
2893                 } else
2894                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2895                   if(buf[i-8] == '-' && buf[i-3] == 't')
2896                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2897                     if(!strcmp("c-shouts", chatPartner[p])) {
2898                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2899                         chattingPartner = p; break;
2900                     }
2901                   }
2902                   if(chattingPartner < 0)
2903                   for(p=0; p<MAX_CHAT; p++) {
2904                     if(!strcmp("shouts", chatPartner[p])) {
2905                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2906                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2907                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2908                         chattingPartner = p; break;
2909                     }
2910                   }
2911                 }
2912                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2913                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2914                     talker[0] = 0; Colorize(ColorTell, FALSE);
2915                     chattingPartner = p; break;
2916                 }
2917                 if(chattingPartner<0) i = oldi; else {
2918                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2919                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2920                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2921                     started = STARTED_COMMENT;
2922                     parse_pos = 0; parse[0] = NULLCHAR;
2923                     savingComment = 3 + chattingPartner; // counts as TRUE
2924                     suppressKibitz = TRUE;
2925                     continue;
2926                 }
2927             } // [HGM] chat: end of patch
2928
2929             if (appData.zippyTalk || appData.zippyPlay) {
2930                 /* [DM] Backup address for color zippy lines */
2931                 backup = i;
2932 #if ZIPPY
2933                if (loggedOn == TRUE)
2934                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2935                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2936 #endif
2937             } // [DM] 'else { ' deleted
2938                 if (
2939                     /* Regular tells and says */
2940                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2941                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2942                     looking_at(buf, &i, "* says: ") ||
2943                     /* Don't color "message" or "messages" output */
2944                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2945                     looking_at(buf, &i, "*. * at *:*: ") ||
2946                     looking_at(buf, &i, "--* (*:*): ") ||
2947                     /* Message notifications (same color as tells) */
2948                     looking_at(buf, &i, "* has left a message ") ||
2949                     looking_at(buf, &i, "* just sent you a message:\n") ||
2950                     /* Whispers and kibitzes */
2951                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2952                     looking_at(buf, &i, "* kibitzes: ") ||
2953                     /* Channel tells */
2954                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2955
2956                   if (tkind == 1 && strchr(star_match[0], ':')) {
2957                       /* Avoid "tells you:" spoofs in channels */
2958                      tkind = 3;
2959                   }
2960                   if (star_match[0][0] == NULLCHAR ||
2961                       strchr(star_match[0], ' ') ||
2962                       (tkind == 3 && strchr(star_match[1], ' '))) {
2963                     /* Reject bogus matches */
2964                     i = oldi;
2965                   } else {
2966                     if (appData.colorize) {
2967                       if (oldi > next_out) {
2968                         SendToPlayer(&buf[next_out], oldi - next_out);
2969                         next_out = oldi;
2970                       }
2971                       switch (tkind) {
2972                       case 1:
2973                         Colorize(ColorTell, FALSE);
2974                         curColor = ColorTell;
2975                         break;
2976                       case 2:
2977                         Colorize(ColorKibitz, FALSE);
2978                         curColor = ColorKibitz;
2979                         break;
2980                       case 3:
2981                         p = strrchr(star_match[1], '(');
2982                         if (p == NULL) {
2983                           p = star_match[1];
2984                         } else {
2985                           p++;
2986                         }
2987                         if (atoi(p) == 1) {
2988                           Colorize(ColorChannel1, FALSE);
2989                           curColor = ColorChannel1;
2990                         } else {
2991                           Colorize(ColorChannel, FALSE);
2992                           curColor = ColorChannel;
2993                         }
2994                         break;
2995                       case 5:
2996                         curColor = ColorNormal;
2997                         break;
2998                       }
2999                     }
3000                     if (started == STARTED_NONE && appData.autoComment &&
3001                         (gameMode == IcsObserving ||
3002                          gameMode == IcsPlayingWhite ||
3003                          gameMode == IcsPlayingBlack)) {
3004                       parse_pos = i - oldi;
3005                       memcpy(parse, &buf[oldi], parse_pos);
3006                       parse[parse_pos] = NULLCHAR;
3007                       started = STARTED_COMMENT;
3008                       savingComment = TRUE;
3009                     } else {
3010                       started = STARTED_CHATTER;
3011                       savingComment = FALSE;
3012                     }
3013                     loggedOn = TRUE;
3014                     continue;
3015                   }
3016                 }
3017
3018                 if (looking_at(buf, &i, "* s-shouts: ") ||
3019                     looking_at(buf, &i, "* c-shouts: ")) {
3020                     if (appData.colorize) {
3021                         if (oldi > next_out) {
3022                             SendToPlayer(&buf[next_out], oldi - next_out);
3023                             next_out = oldi;
3024                         }
3025                         Colorize(ColorSShout, FALSE);
3026                         curColor = ColorSShout;
3027                     }
3028                     loggedOn = TRUE;
3029                     started = STARTED_CHATTER;
3030                     continue;
3031                 }
3032
3033                 if (looking_at(buf, &i, "--->")) {
3034                     loggedOn = TRUE;
3035                     continue;
3036                 }
3037
3038                 if (looking_at(buf, &i, "* shouts: ") ||
3039                     looking_at(buf, &i, "--> ")) {
3040                     if (appData.colorize) {
3041                         if (oldi > next_out) {
3042                             SendToPlayer(&buf[next_out], oldi - next_out);
3043                             next_out = oldi;
3044                         }
3045                         Colorize(ColorShout, FALSE);
3046                         curColor = ColorShout;
3047                     }
3048                     loggedOn = TRUE;
3049                     started = STARTED_CHATTER;
3050                     continue;
3051                 }
3052
3053                 if (looking_at( buf, &i, "Challenge:")) {
3054                     if (appData.colorize) {
3055                         if (oldi > next_out) {
3056                             SendToPlayer(&buf[next_out], oldi - next_out);
3057                             next_out = oldi;
3058                         }
3059                         Colorize(ColorChallenge, FALSE);
3060                         curColor = ColorChallenge;
3061                     }
3062                     loggedOn = TRUE;
3063                     continue;
3064                 }
3065
3066                 if (looking_at(buf, &i, "* offers you") ||
3067                     looking_at(buf, &i, "* offers to be") ||
3068                     looking_at(buf, &i, "* would like to") ||
3069                     looking_at(buf, &i, "* requests to") ||
3070                     looking_at(buf, &i, "Your opponent offers") ||
3071                     looking_at(buf, &i, "Your opponent requests")) {
3072
3073                     if (appData.colorize) {
3074                         if (oldi > next_out) {
3075                             SendToPlayer(&buf[next_out], oldi - next_out);
3076                             next_out = oldi;
3077                         }
3078                         Colorize(ColorRequest, FALSE);
3079                         curColor = ColorRequest;
3080                     }
3081                     continue;
3082                 }
3083
3084                 if (looking_at(buf, &i, "* (*) seeking")) {
3085                     if (appData.colorize) {
3086                         if (oldi > next_out) {
3087                             SendToPlayer(&buf[next_out], oldi - next_out);
3088                             next_out = oldi;
3089                         }
3090                         Colorize(ColorSeek, FALSE);
3091                         curColor = ColorSeek;
3092                     }
3093                     continue;
3094             }
3095
3096             if (looking_at(buf, &i, "\\   ")) {
3097                 if (prevColor != ColorNormal) {
3098                     if (oldi > next_out) {
3099                         SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = oldi;
3101                     }
3102                     Colorize(prevColor, TRUE);
3103                     curColor = prevColor;
3104                 }
3105                 if (savingComment) {
3106                     parse_pos = i - oldi;
3107                     memcpy(parse, &buf[oldi], parse_pos);
3108                     parse[parse_pos] = NULLCHAR;
3109                     started = STARTED_COMMENT;
3110                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3111                         chattingPartner = savingComment - 3; // kludge to remember the box
3112                 } else {
3113                     started = STARTED_CHATTER;
3114                 }
3115                 continue;
3116             }
3117
3118             if (looking_at(buf, &i, "Black Strength :") ||
3119                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3120                 looking_at(buf, &i, "<10>") ||
3121                 looking_at(buf, &i, "#@#")) {
3122                 /* Wrong board style */
3123                 loggedOn = TRUE;
3124                 SendToICS(ics_prefix);
3125                 SendToICS("set style 12\n");
3126                 SendToICS(ics_prefix);
3127                 SendToICS("refresh\n");
3128                 continue;
3129             }
3130
3131             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3132                 ICSInitScript();
3133                 have_sent_ICS_logon = 1;
3134                 continue;
3135             }
3136
3137             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3138                 (looking_at(buf, &i, "\n<12> ") ||
3139                  looking_at(buf, &i, "<12> "))) {
3140                 loggedOn = TRUE;
3141                 if (oldi > next_out) {
3142                     SendToPlayer(&buf[next_out], oldi - next_out);
3143                 }
3144                 next_out = i;
3145                 started = STARTED_BOARD;
3146                 parse_pos = 0;
3147                 continue;
3148             }
3149
3150             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3151                 looking_at(buf, &i, "<b1> ")) {
3152                 if (oldi > next_out) {
3153                     SendToPlayer(&buf[next_out], oldi - next_out);
3154                 }
3155                 next_out = i;
3156                 started = STARTED_HOLDINGS;
3157                 parse_pos = 0;
3158                 continue;
3159             }
3160
3161             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3162                 loggedOn = TRUE;
3163                 /* Header for a move list -- first line */
3164
3165                 switch (ics_getting_history) {
3166                   case H_FALSE:
3167                     switch (gameMode) {
3168                       case IcsIdle:
3169                       case BeginningOfGame:
3170                         /* User typed "moves" or "oldmoves" while we
3171                            were idle.  Pretend we asked for these
3172                            moves and soak them up so user can step
3173                            through them and/or save them.
3174                            */
3175                         Reset(FALSE, TRUE);
3176                         gameMode = IcsObserving;
3177                         ModeHighlight();
3178                         ics_gamenum = -1;
3179                         ics_getting_history = H_GOT_UNREQ_HEADER;
3180                         break;
3181                       case EditGame: /*?*/
3182                       case EditPosition: /*?*/
3183                         /* Should above feature work in these modes too? */
3184                         /* For now it doesn't */
3185                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3186                         break;
3187                       default:
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                     }
3191                     break;
3192                   case H_REQUESTED:
3193                     /* Is this the right one? */
3194                     if (gameInfo.white && gameInfo.black &&
3195                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3196                         strcmp(gameInfo.black, star_match[2]) == 0) {
3197                         /* All is well */
3198                         ics_getting_history = H_GOT_REQ_HEADER;
3199                     }
3200                     break;
3201                   case H_GOT_REQ_HEADER:
3202                   case H_GOT_UNREQ_HEADER:
3203                   case H_GOT_UNWANTED_HEADER:
3204                   case H_GETTING_MOVES:
3205                     /* Should not happen */
3206                     DisplayError(_("Error gathering move list: two headers"), 0);
3207                     ics_getting_history = H_FALSE;
3208                     break;
3209                 }
3210
3211                 /* Save player ratings into gameInfo if needed */
3212                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3213                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3214                     (gameInfo.whiteRating == -1 ||
3215                      gameInfo.blackRating == -1)) {
3216
3217                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3218                     gameInfo.blackRating = string_to_rating(star_match[3]);
3219                     if (appData.debugMode)
3220                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3221                               gameInfo.whiteRating, gameInfo.blackRating);
3222                 }
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i,
3227               "* * match, initial time: * minute*, increment: * second")) {
3228                 /* Header for a move list -- second line */
3229                 /* Initial board will follow if this is a wild game */
3230                 if (gameInfo.event != NULL) free(gameInfo.event);
3231                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3232                 gameInfo.event = StrSave(str);
3233                 /* [HGM] we switched variant. Translate boards if needed. */
3234                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3235                 continue;
3236             }
3237
3238             if (looking_at(buf, &i, "Move  ")) {
3239                 /* Beginning of a move list */
3240                 switch (ics_getting_history) {
3241                   case H_FALSE:
3242                     /* Normally should not happen */
3243                     /* Maybe user hit reset while we were parsing */
3244                     break;
3245                   case H_REQUESTED:
3246                     /* Happens if we are ignoring a move list that is not
3247                      * the one we just requested.  Common if the user
3248                      * tries to observe two games without turning off
3249                      * getMoveList */
3250                     break;
3251                   case H_GETTING_MOVES:
3252                     /* Should not happen */
3253                     DisplayError(_("Error gathering move list: nested"), 0);
3254                     ics_getting_history = H_FALSE;
3255                     break;
3256                   case H_GOT_REQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES;
3259                     parse_pos = 0;
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                     }
3263                     break;
3264                   case H_GOT_UNREQ_HEADER:
3265                     ics_getting_history = H_GETTING_MOVES;
3266                     started = STARTED_MOVES_NOHIDE;
3267                     parse_pos = 0;
3268                     break;
3269                   case H_GOT_UNWANTED_HEADER:
3270                     ics_getting_history = H_FALSE;
3271                     break;
3272                 }
3273                 continue;
3274             }
3275
3276             if (looking_at(buf, &i, "% ") ||
3277                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3278                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3279                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3280                     soughtPending = FALSE;
3281                     seekGraphUp = TRUE;
3282                     DrawSeekGraph();
3283                 }
3284                 if(suppressKibitz) next_out = i;
3285                 savingComment = FALSE;
3286                 suppressKibitz = 0;
3287                 switch (started) {
3288                   case STARTED_MOVES:
3289                   case STARTED_MOVES_NOHIDE:
3290                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3291                     parse[parse_pos + i - oldi] = NULLCHAR;
3292                     ParseGameHistory(parse);
3293 #if ZIPPY
3294                     if (appData.zippyPlay && first.initDone) {
3295                         FeedMovesToProgram(&first, forwardMostMove);
3296                         if (gameMode == IcsPlayingWhite) {
3297                             if (WhiteOnMove(forwardMostMove)) {
3298                                 if (first.sendTime) {
3299                                   if (first.useColors) {
3300                                     SendToProgram("black\n", &first);
3301                                   }
3302                                   SendTimeRemaining(&first, TRUE);
3303                                 }
3304                                 if (first.useColors) {
3305                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3306                                 }
3307                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3308                                 first.maybeThinking = TRUE;
3309                             } else {
3310                                 if (first.usePlayother) {
3311                                   if (first.sendTime) {
3312                                     SendTimeRemaining(&first, TRUE);
3313                                   }
3314                                   SendToProgram("playother\n", &first);
3315                                   firstMove = FALSE;
3316                                 } else {
3317                                   firstMove = TRUE;
3318                                 }
3319                             }
3320                         } else if (gameMode == IcsPlayingBlack) {
3321                             if (!WhiteOnMove(forwardMostMove)) {
3322                                 if (first.sendTime) {
3323                                   if (first.useColors) {
3324                                     SendToProgram("white\n", &first);
3325                                   }
3326                                   SendTimeRemaining(&first, FALSE);
3327                                 }
3328                                 if (first.useColors) {
3329                                   SendToProgram("black\n", &first);
3330                                 }
3331                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3332                                 first.maybeThinking = TRUE;
3333                             } else {
3334                                 if (first.usePlayother) {
3335                                   if (first.sendTime) {
3336                                     SendTimeRemaining(&first, FALSE);
3337                                   }
3338                                   SendToProgram("playother\n", &first);
3339                                   firstMove = FALSE;
3340                                 } else {
3341                                   firstMove = TRUE;
3342                                 }
3343                             }
3344                         }
3345                     }
3346 #endif
3347                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3348                         /* Moves came from oldmoves or moves command
3349                            while we weren't doing anything else.
3350                            */
3351                         currentMove = forwardMostMove;
3352                         ClearHighlights();/*!!could figure this out*/
3353                         flipView = appData.flipView;
3354                         DrawPosition(TRUE, boards[currentMove]);
3355                         DisplayBothClocks();
3356                         snprintf(str, MSG_SIZ, "%s vs. %s",
3357                                 gameInfo.white, gameInfo.black);
3358                         DisplayTitle(str);
3359                         gameMode = IcsIdle;
3360                     } else {
3361                         /* Moves were history of an active game */
3362                         if (gameInfo.resultDetails != NULL) {
3363                             free(gameInfo.resultDetails);
3364                             gameInfo.resultDetails = NULL;
3365                         }
3366                     }
3367                     HistorySet(parseList, backwardMostMove,
3368                                forwardMostMove, currentMove-1);
3369                     DisplayMove(currentMove - 1);
3370                     if (started == STARTED_MOVES) next_out = i;
3371                     started = STARTED_NONE;
3372                     ics_getting_history = H_FALSE;
3373                     break;
3374
3375                   case STARTED_OBSERVE:
3376                     started = STARTED_NONE;
3377                     SendToICS(ics_prefix);
3378                     SendToICS("refresh\n");
3379                     break;
3380
3381                   default:
3382                     break;
3383                 }
3384                 if(bookHit) { // [HGM] book: simulate book reply
3385                     static char bookMove[MSG_SIZ]; // a bit generous?
3386
3387                     programStats.nodes = programStats.depth = programStats.time =
3388                     programStats.score = programStats.got_only_move = 0;
3389                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3390
3391                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3392                     strcat(bookMove, bookHit);
3393                     HandleMachineMove(bookMove, &first);
3394                 }
3395                 continue;
3396             }
3397
3398             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3399                  started == STARTED_HOLDINGS ||
3400                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3401                 /* Accumulate characters in move list or board */
3402                 parse[parse_pos++] = buf[i];
3403             }
3404
3405             /* Start of game messages.  Mostly we detect start of game
3406                when the first board image arrives.  On some versions
3407                of the ICS, though, we need to do a "refresh" after starting
3408                to observe in order to get the current board right away. */
3409             if (looking_at(buf, &i, "Adding game * to observation list")) {
3410                 started = STARTED_OBSERVE;
3411                 continue;
3412             }
3413
3414             /* Handle auto-observe */
3415             if (appData.autoObserve &&
3416                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3417                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3418                 char *player;
3419                 /* Choose the player that was highlighted, if any. */
3420                 if (star_match[0][0] == '\033' ||
3421                     star_match[1][0] != '\033') {
3422                     player = star_match[0];
3423                 } else {
3424                     player = star_match[2];
3425                 }
3426                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3427                         ics_prefix, StripHighlightAndTitle(player));
3428                 SendToICS(str);
3429
3430                 /* Save ratings from notify string */
3431                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3432                 player1Rating = string_to_rating(star_match[1]);
3433                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3434                 player2Rating = string_to_rating(star_match[3]);
3435
3436                 if (appData.debugMode)
3437                   fprintf(debugFP,
3438                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3439                           player1Name, player1Rating,
3440                           player2Name, player2Rating);
3441
3442                 continue;
3443             }
3444
3445             /* Deal with automatic examine mode after a game,
3446                and with IcsObserving -> IcsExamining transition */
3447             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3448                 looking_at(buf, &i, "has made you an examiner of game *")) {
3449
3450                 int gamenum = atoi(star_match[0]);
3451                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3452                     gamenum == ics_gamenum) {
3453                     /* We were already playing or observing this game;
3454                        no need to refetch history */
3455                     gameMode = IcsExamining;
3456                     if (pausing) {
3457                         pauseExamForwardMostMove = forwardMostMove;
3458                     } else if (currentMove < forwardMostMove) {
3459                         ForwardInner(forwardMostMove);
3460                     }
3461                 } else {
3462                     /* I don't think this case really can happen */
3463                     SendToICS(ics_prefix);
3464                     SendToICS("refresh\n");
3465                 }
3466                 continue;
3467             }
3468
3469             /* Error messages */
3470 //          if (ics_user_moved) {
3471             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3472                 if (looking_at(buf, &i, "Illegal move") ||
3473                     looking_at(buf, &i, "Not a legal move") ||
3474                     looking_at(buf, &i, "Your king is in check") ||
3475                     looking_at(buf, &i, "It isn't your turn") ||
3476                     looking_at(buf, &i, "It is not your move")) {
3477                     /* Illegal move */
3478                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3479                         currentMove = forwardMostMove-1;
3480                         DisplayMove(currentMove - 1); /* before DMError */
3481                         DrawPosition(FALSE, boards[currentMove]);
3482                         SwitchClocks(forwardMostMove-1); // [HGM] race
3483                         DisplayBothClocks();
3484                     }
3485                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3486                     ics_user_moved = 0;
3487                     continue;
3488                 }
3489             }
3490
3491             if (looking_at(buf, &i, "still have time") ||
3492                 looking_at(buf, &i, "not out of time") ||
3493                 looking_at(buf, &i, "either player is out of time") ||
3494                 looking_at(buf, &i, "has timeseal; checking")) {
3495                 /* We must have called his flag a little too soon */
3496                 whiteFlag = blackFlag = FALSE;
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "added * seconds to") ||
3501                 looking_at(buf, &i, "seconds were added to")) {
3502                 /* Update the clocks */
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3509                 ics_clock_paused = TRUE;
3510                 StopClocks();
3511                 continue;
3512             }
3513
3514             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3515                 ics_clock_paused = FALSE;
3516                 StartClocks();
3517                 continue;
3518             }
3519
3520             /* Grab player ratings from the Creating: message.
3521                Note we have to check for the special case when
3522                the ICS inserts things like [white] or [black]. */
3523             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3524                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3525                 /* star_matches:
3526                    0    player 1 name (not necessarily white)
3527                    1    player 1 rating
3528                    2    empty, white, or black (IGNORED)
3529                    3    player 2 name (not necessarily black)
3530                    4    player 2 rating
3531
3532                    The names/ratings are sorted out when the game
3533                    actually starts (below).
3534                 */
3535                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3536                 player1Rating = string_to_rating(star_match[1]);
3537                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3538                 player2Rating = string_to_rating(star_match[4]);
3539
3540                 if (appData.debugMode)
3541                   fprintf(debugFP,
3542                           "Ratings from 'Creating:' %s %d, %s %d\n",
3543                           player1Name, player1Rating,
3544                           player2Name, player2Rating);
3545
3546                 continue;
3547             }
3548
3549             /* Improved generic start/end-of-game messages */
3550             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3551                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3552                 /* If tkind == 0: */
3553                 /* star_match[0] is the game number */
3554                 /*           [1] is the white player's name */
3555                 /*           [2] is the black player's name */
3556                 /* For end-of-game: */
3557                 /*           [3] is the reason for the game end */
3558                 /*           [4] is a PGN end game-token, preceded by " " */
3559                 /* For start-of-game: */
3560                 /*           [3] begins with "Creating" or "Continuing" */
3561                 /*           [4] is " *" or empty (don't care). */
3562                 int gamenum = atoi(star_match[0]);
3563                 char *whitename, *blackname, *why, *endtoken;
3564                 ChessMove endtype = EndOfFile;
3565
3566                 if (tkind == 0) {
3567                   whitename = star_match[1];
3568                   blackname = star_match[2];
3569                   why = star_match[3];
3570                   endtoken = star_match[4];
3571                 } else {
3572                   whitename = star_match[1];
3573                   blackname = star_match[3];
3574                   why = star_match[5];
3575                   endtoken = star_match[6];
3576                 }
3577
3578                 /* Game start messages */
3579                 if (strncmp(why, "Creating ", 9) == 0 ||
3580                     strncmp(why, "Continuing ", 11) == 0) {
3581                     gs_gamenum = gamenum;
3582                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3583                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3584 #if ZIPPY
3585                     if (appData.zippyPlay) {
3586                         ZippyGameStart(whitename, blackname);
3587                     }
3588 #endif /*ZIPPY*/
3589                     partnerBoardValid = FALSE; // [HGM] bughouse
3590                     continue;
3591                 }
3592
3593                 /* Game end messages */
3594                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3595                     ics_gamenum != gamenum) {
3596                     continue;
3597                 }
3598                 while (endtoken[0] == ' ') endtoken++;
3599                 switch (endtoken[0]) {
3600                   case '*':
3601                   default:
3602                     endtype = GameUnfinished;
3603                     break;
3604                   case '0':
3605                     endtype = BlackWins;
3606                     break;
3607                   case '1':
3608                     if (endtoken[1] == '/')
3609                       endtype = GameIsDrawn;
3610                     else
3611                       endtype = WhiteWins;
3612                     break;
3613                 }
3614                 GameEnds(endtype, why, GE_ICS);
3615 #if ZIPPY
3616                 if (appData.zippyPlay && first.initDone) {
3617                     ZippyGameEnd(endtype, why);
3618                     if (first.pr == NULL) {
3619                       /* Start the next process early so that we'll
3620                          be ready for the next challenge */
3621                       StartChessProgram(&first);
3622                     }
3623                     /* Send "new" early, in case this command takes
3624                        a long time to finish, so that we'll be ready
3625                        for the next challenge. */
3626                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3627                     Reset(TRUE, TRUE);
3628                 }
3629 #endif /*ZIPPY*/
3630                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "Removing game * from observation") ||
3635                 looking_at(buf, &i, "no longer observing game *") ||
3636                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3637                 if (gameMode == IcsObserving &&
3638                     atoi(star_match[0]) == ics_gamenum)
3639                   {
3640                       /* icsEngineAnalyze */
3641                       if (appData.icsEngineAnalyze) {
3642                             ExitAnalyzeMode();
3643                             ModeHighlight();
3644                       }
3645                       StopClocks();
3646                       gameMode = IcsIdle;
3647                       ics_gamenum = -1;
3648                       ics_user_moved = FALSE;
3649                   }
3650                 continue;
3651             }
3652
3653             if (looking_at(buf, &i, "no longer examining game *")) {
3654                 if (gameMode == IcsExamining &&
3655                     atoi(star_match[0]) == ics_gamenum)
3656                   {
3657                       gameMode = IcsIdle;
3658                       ics_gamenum = -1;
3659                       ics_user_moved = FALSE;
3660                   }
3661                 continue;
3662             }
3663
3664             /* Advance leftover_start past any newlines we find,
3665                so only partial lines can get reparsed */
3666             if (looking_at(buf, &i, "\n")) {
3667                 prevColor = curColor;
3668                 if (curColor != ColorNormal) {
3669                     if (oldi > next_out) {
3670                         SendToPlayer(&buf[next_out], oldi - next_out);
3671                         next_out = oldi;
3672                     }
3673                     Colorize(ColorNormal, FALSE);
3674                     curColor = ColorNormal;
3675                 }
3676                 if (started == STARTED_BOARD) {
3677                     started = STARTED_NONE;
3678                     parse[parse_pos] = NULLCHAR;
3679                     ParseBoard12(parse);
3680                     ics_user_moved = 0;
3681
3682                     /* Send premove here */
3683                     if (appData.premove) {
3684                       char str[MSG_SIZ];
3685                       if (currentMove == 0 &&
3686                           gameMode == IcsPlayingWhite &&
3687                           appData.premoveWhite) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (currentMove == 1 &&
3693                                  gameMode == IcsPlayingBlack &&
3694                                  appData.premoveBlack) {
3695                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3696                         if (appData.debugMode)
3697                           fprintf(debugFP, "Sending premove:\n");
3698                         SendToICS(str);
3699                       } else if (gotPremove) {
3700                         gotPremove = 0;
3701                         ClearPremoveHighlights();
3702                         if (appData.debugMode)
3703                           fprintf(debugFP, "Sending premove:\n");
3704                           UserMoveEvent(premoveFromX, premoveFromY,
3705                                         premoveToX, premoveToY,
3706                                         premovePromoChar);
3707                       }
3708                     }
3709
3710                     /* Usually suppress following prompt */
3711                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3712                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3713                         if (looking_at(buf, &i, "*% ")) {
3714                             savingComment = FALSE;
3715                             suppressKibitz = 0;
3716                         }
3717                     }
3718                     next_out = i;
3719                 } else if (started == STARTED_HOLDINGS) {
3720                     int gamenum;
3721                     char new_piece[MSG_SIZ];
3722                     started = STARTED_NONE;
3723                     parse[parse_pos] = NULLCHAR;
3724                     if (appData.debugMode)
3725                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3726                                                         parse, currentMove);
3727                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3728                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3729                         if (gameInfo.variant == VariantNormal) {
3730                           /* [HGM] We seem to switch variant during a game!
3731                            * Presumably no holdings were displayed, so we have
3732                            * to move the position two files to the right to
3733                            * create room for them!
3734                            */
3735                           VariantClass newVariant;
3736                           switch(gameInfo.boardWidth) { // base guess on board width
3737                                 case 9:  newVariant = VariantShogi; break;
3738                                 case 10: newVariant = VariantGreat; break;
3739                                 default: newVariant = VariantCrazyhouse; break;
3740                           }
3741                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3742                           /* Get a move list just to see the header, which
3743                              will tell us whether this is really bug or zh */
3744                           if (ics_getting_history == H_FALSE) {
3745                             ics_getting_history = H_REQUESTED;
3746                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3747                             SendToICS(str);
3748                           }
3749                         }
3750                         new_piece[0] = NULLCHAR;
3751                         sscanf(parse, "game %d white [%s black [%s <- %s",
3752                                &gamenum, white_holding, black_holding,
3753                                new_piece);
3754                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3755                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3756                         /* [HGM] copy holdings to board holdings area */
3757                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3758                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3759                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3760 #if ZIPPY
3761                         if (appData.zippyPlay && first.initDone) {
3762                             ZippyHoldings(white_holding, black_holding,
3763                                           new_piece);
3764                         }
3765 #endif /*ZIPPY*/
3766                         if (tinyLayout || smallLayout) {
3767                             char wh[16], bh[16];
3768                             PackHolding(wh, white_holding);
3769                             PackHolding(bh, black_holding);
3770                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3771                                     gameInfo.white, gameInfo.black);
3772                         } else {
3773                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3774                                     gameInfo.white, white_holding,
3775                                     gameInfo.black, black_holding);
3776                         }
3777                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3778                         DrawPosition(FALSE, boards[currentMove]);
3779                         DisplayTitle(str);
3780                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3781                         sscanf(parse, "game %d white [%s black [%s <- %s",
3782                                &gamenum, white_holding, black_holding,
3783                                new_piece);
3784                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3785                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3786                         /* [HGM] copy holdings to partner-board holdings area */
3787                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3788                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3789                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3790                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3791                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3792                       }
3793                     }
3794                     /* Suppress following prompt */
3795                     if (looking_at(buf, &i, "*% ")) {
3796                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3797                         savingComment = FALSE;
3798                         suppressKibitz = 0;
3799                     }
3800                     next_out = i;
3801                 }
3802                 continue;
3803             }
3804
3805             i++;                /* skip unparsed character and loop back */
3806         }
3807
3808         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3809 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3810 //          SendToPlayer(&buf[next_out], i - next_out);
3811             started != STARTED_HOLDINGS && leftover_start > next_out) {
3812             SendToPlayer(&buf[next_out], leftover_start - next_out);
3813             next_out = i;
3814         }
3815
3816         leftover_len = buf_len - leftover_start;
3817         /* if buffer ends with something we couldn't parse,
3818            reparse it after appending the next read */
3819
3820     } else if (count == 0) {
3821         RemoveInputSource(isr);
3822         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3823     } else {
3824         DisplayFatalError(_("Error reading from ICS"), error, 1);
3825     }
3826 }
3827
3828
3829 /* Board style 12 looks like this:
3830
3831    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3832
3833  * The "<12> " is stripped before it gets to this routine.  The two
3834  * trailing 0's (flip state and clock ticking) are later addition, and
3835  * some chess servers may not have them, or may have only the first.
3836  * Additional trailing fields may be added in the future.
3837  */
3838
3839 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3840
3841 #define RELATION_OBSERVING_PLAYED    0
3842 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3843 #define RELATION_PLAYING_MYMOVE      1
3844 #define RELATION_PLAYING_NOTMYMOVE  -1
3845 #define RELATION_EXAMINING           2
3846 #define RELATION_ISOLATED_BOARD     -3
3847 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3848
3849 void
3850 ParseBoard12(string)
3851      char *string;
3852 {
3853     GameMode newGameMode;
3854     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3855     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3856     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3857     char to_play, board_chars[200];
3858     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3859     char black[32], white[32];
3860     Board board;
3861     int prevMove = currentMove;
3862     int ticking = 2;
3863     ChessMove moveType;
3864     int fromX, fromY, toX, toY;
3865     char promoChar;
3866     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3867     char *bookHit = NULL; // [HGM] book
3868     Boolean weird = FALSE, reqFlag = FALSE;
3869
3870     fromX = fromY = toX = toY = -1;
3871
3872     newGame = FALSE;
3873
3874     if (appData.debugMode)
3875       fprintf(debugFP, _("Parsing board: %s\n"), string);
3876
3877     move_str[0] = NULLCHAR;
3878     elapsed_time[0] = NULLCHAR;
3879     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3880         int  i = 0, j;
3881         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3882             if(string[i] == ' ') { ranks++; files = 0; }
3883             else files++;
3884             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3885             i++;
3886         }
3887         for(j = 0; j <i; j++) board_chars[j] = string[j];
3888         board_chars[i] = '\0';
3889         string += i + 1;
3890     }
3891     n = sscanf(string, PATTERN, &to_play, &double_push,
3892                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3893                &gamenum, white, black, &relation, &basetime, &increment,
3894                &white_stren, &black_stren, &white_time, &black_time,
3895                &moveNum, str, elapsed_time, move_str, &ics_flip,
3896                &ticking);
3897
3898     if (n < 21) {
3899         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3900         DisplayError(str, 0);
3901         return;
3902     }
3903
3904     /* Convert the move number to internal form */
3905     moveNum = (moveNum - 1) * 2;
3906     if (to_play == 'B') moveNum++;
3907     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3908       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3909                         0, 1);
3910       return;
3911     }
3912
3913     switch (relation) {
3914       case RELATION_OBSERVING_PLAYED:
3915       case RELATION_OBSERVING_STATIC:
3916         if (gamenum == -1) {
3917             /* Old ICC buglet */
3918             relation = RELATION_OBSERVING_STATIC;
3919         }
3920         newGameMode = IcsObserving;
3921         break;
3922       case RELATION_PLAYING_MYMOVE:
3923       case RELATION_PLAYING_NOTMYMOVE:
3924         newGameMode =
3925           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3926             IcsPlayingWhite : IcsPlayingBlack;
3927         break;
3928       case RELATION_EXAMINING:
3929         newGameMode = IcsExamining;
3930         break;
3931       case RELATION_ISOLATED_BOARD:
3932       default:
3933         /* Just display this board.  If user was doing something else,
3934            we will forget about it until the next board comes. */
3935         newGameMode = IcsIdle;
3936         break;
3937       case RELATION_STARTING_POSITION:
3938         newGameMode = gameMode;
3939         break;
3940     }
3941
3942     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3943          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3944       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3945       char *toSqr;
3946       for (k = 0; k < ranks; k++) {
3947         for (j = 0; j < files; j++)
3948           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3949         if(gameInfo.holdingsWidth > 1) {
3950              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3951              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3952         }
3953       }
3954       CopyBoard(partnerBoard, board);
3955       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3956         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3957         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3958       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3959       if(toSqr = strchr(str, '-')) {
3960         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3961         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3962       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3963       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3964       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3965       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3966       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3967       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3968                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3969       DisplayMessage(partnerStatus, "");
3970         partnerBoardValid = TRUE;
3971       return;
3972     }
3973
3974     /* Modify behavior for initial board display on move listing
3975        of wild games.
3976        */
3977     switch (ics_getting_history) {
3978       case H_FALSE:
3979       case H_REQUESTED:
3980         break;
3981       case H_GOT_REQ_HEADER:
3982       case H_GOT_UNREQ_HEADER:
3983         /* This is the initial position of the current game */
3984         gamenum = ics_gamenum;
3985         moveNum = 0;            /* old ICS bug workaround */
3986         if (to_play == 'B') {
3987           startedFromSetupPosition = TRUE;
3988           blackPlaysFirst = TRUE;
3989           moveNum = 1;
3990           if (forwardMostMove == 0) forwardMostMove = 1;
3991           if (backwardMostMove == 0) backwardMostMove = 1;
3992           if (currentMove == 0) currentMove = 1;
3993         }
3994         newGameMode = gameMode;
3995         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3996         break;
3997       case H_GOT_UNWANTED_HEADER:
3998         /* This is an initial board that we don't want */
3999         return;
4000       case H_GETTING_MOVES:
4001         /* Should not happen */
4002         DisplayError(_("Error gathering move list: extra board"), 0);
4003         ics_getting_history = H_FALSE;
4004         return;
4005     }
4006
4007    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4008                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4009      /* [HGM] We seem to have switched variant unexpectedly
4010       * Try to guess new variant from board size
4011       */
4012           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4013           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4014           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4015           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4016           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4017           if(!weird) newVariant = VariantNormal;
4018           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4019           /* Get a move list just to see the header, which
4020              will tell us whether this is really bug or zh */
4021           if (ics_getting_history == H_FALSE) {
4022             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4023             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4024             SendToICS(str);
4025           }
4026     }
4027
4028     /* Take action if this is the first board of a new game, or of a
4029        different game than is currently being displayed.  */
4030     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4031         relation == RELATION_ISOLATED_BOARD) {
4032
4033         /* Forget the old game and get the history (if any) of the new one */
4034         if (gameMode != BeginningOfGame) {
4035           Reset(TRUE, TRUE);
4036         }
4037         newGame = TRUE;
4038         if (appData.autoRaiseBoard) BoardToTop();
4039         prevMove = -3;
4040         if (gamenum == -1) {
4041             newGameMode = IcsIdle;
4042         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4043                    appData.getMoveList && !reqFlag) {
4044             /* Need to get game history */
4045             ics_getting_history = H_REQUESTED;
4046             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4047             SendToICS(str);
4048         }
4049
4050         /* Initially flip the board to have black on the bottom if playing
4051            black or if the ICS flip flag is set, but let the user change
4052            it with the Flip View button. */
4053         flipView = appData.autoFlipView ?
4054           (newGameMode == IcsPlayingBlack) || ics_flip :
4055           appData.flipView;
4056
4057         /* Done with values from previous mode; copy in new ones */
4058         gameMode = newGameMode;
4059         ModeHighlight();
4060         ics_gamenum = gamenum;
4061         if (gamenum == gs_gamenum) {
4062             int klen = strlen(gs_kind);
4063             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4064             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4065             gameInfo.event = StrSave(str);
4066         } else {
4067             gameInfo.event = StrSave("ICS game");
4068         }
4069         gameInfo.site = StrSave(appData.icsHost);
4070         gameInfo.date = PGNDate();
4071         gameInfo.round = StrSave("-");
4072         gameInfo.white = StrSave(white);
4073         gameInfo.black = StrSave(black);
4074         timeControl = basetime * 60 * 1000;
4075         timeControl_2 = 0;
4076         timeIncrement = increment * 1000;
4077         movesPerSession = 0;
4078         gameInfo.timeControl = TimeControlTagValue();
4079         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4080   if (appData.debugMode) {
4081     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4082     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4083     setbuf(debugFP, NULL);
4084   }
4085
4086         gameInfo.outOfBook = NULL;
4087
4088         /* Do we have the ratings? */
4089         if (strcmp(player1Name, white) == 0 &&
4090             strcmp(player2Name, black) == 0) {
4091             if (appData.debugMode)
4092               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093                       player1Rating, player2Rating);
4094             gameInfo.whiteRating = player1Rating;
4095             gameInfo.blackRating = player2Rating;
4096         } else if (strcmp(player2Name, white) == 0 &&
4097                    strcmp(player1Name, black) == 0) {
4098             if (appData.debugMode)
4099               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4100                       player2Rating, player1Rating);
4101             gameInfo.whiteRating = player2Rating;
4102             gameInfo.blackRating = player1Rating;
4103         }
4104         player1Name[0] = player2Name[0] = NULLCHAR;
4105
4106         /* Silence shouts if requested */
4107         if (appData.quietPlay &&
4108             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4109             SendToICS(ics_prefix);
4110             SendToICS("set shout 0\n");
4111         }
4112     }
4113
4114     /* Deal with midgame name changes */
4115     if (!newGame) {
4116         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4117             if (gameInfo.white) free(gameInfo.white);
4118             gameInfo.white = StrSave(white);
4119         }
4120         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4121             if (gameInfo.black) free(gameInfo.black);
4122             gameInfo.black = StrSave(black);
4123         }
4124     }
4125
4126     /* Throw away game result if anything actually changes in examine mode */
4127     if (gameMode == IcsExamining && !newGame) {
4128         gameInfo.result = GameUnfinished;
4129         if (gameInfo.resultDetails != NULL) {
4130             free(gameInfo.resultDetails);
4131             gameInfo.resultDetails = NULL;
4132         }
4133     }
4134
4135     /* In pausing && IcsExamining mode, we ignore boards coming
4136        in if they are in a different variation than we are. */
4137     if (pauseExamInvalid) return;
4138     if (pausing && gameMode == IcsExamining) {
4139         if (moveNum <= pauseExamForwardMostMove) {
4140             pauseExamInvalid = TRUE;
4141             forwardMostMove = pauseExamForwardMostMove;
4142             return;
4143         }
4144     }
4145
4146   if (appData.debugMode) {
4147     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4148   }
4149     /* Parse the board */
4150     for (k = 0; k < ranks; k++) {
4151       for (j = 0; j < files; j++)
4152         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4153       if(gameInfo.holdingsWidth > 1) {
4154            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4155            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4156       }
4157     }
4158     CopyBoard(boards[moveNum], board);
4159     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4160     if (moveNum == 0) {
4161         startedFromSetupPosition =
4162           !CompareBoards(board, initialPosition);
4163         if(startedFromSetupPosition)
4164             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4165     }
4166
4167     /* [HGM] Set castling rights. Take the outermost Rooks,
4168        to make it also work for FRC opening positions. Note that board12
4169        is really defective for later FRC positions, as it has no way to
4170        indicate which Rook can castle if they are on the same side of King.
4171        For the initial position we grant rights to the outermost Rooks,
4172        and remember thos rights, and we then copy them on positions
4173        later in an FRC game. This means WB might not recognize castlings with
4174        Rooks that have moved back to their original position as illegal,
4175        but in ICS mode that is not its job anyway.
4176     */
4177     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4178     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4179
4180         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4181             if(board[0][i] == WhiteRook) j = i;
4182         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4183         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4187             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4188         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192
4193         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4194         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4195             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4196         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4197             if(board[BOARD_HEIGHT-1][k] == bKing)
4198                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4199         if(gameInfo.variant == VariantTwoKings) {
4200             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4201             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4202             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4203         }
4204     } else { int r;
4205         r = boards[moveNum][CASTLING][0] = initialRights[0];
4206         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4207         r = boards[moveNum][CASTLING][1] = initialRights[1];
4208         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4209         r = boards[moveNum][CASTLING][3] = initialRights[3];
4210         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4211         r = boards[moveNum][CASTLING][4] = initialRights[4];
4212         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4213         /* wildcastle kludge: always assume King has rights */
4214         r = boards[moveNum][CASTLING][2] = initialRights[2];
4215         r = boards[moveNum][CASTLING][5] = initialRights[5];
4216     }
4217     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4218     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4219
4220
4221     if (ics_getting_history == H_GOT_REQ_HEADER ||
4222         ics_getting_history == H_GOT_UNREQ_HEADER) {
4223         /* This was an initial position from a move list, not
4224            the current position */
4225         return;
4226     }
4227
4228     /* Update currentMove and known move number limits */
4229     newMove = newGame || moveNum > forwardMostMove;
4230
4231     if (newGame) {
4232         forwardMostMove = backwardMostMove = currentMove = moveNum;
4233         if (gameMode == IcsExamining && moveNum == 0) {
4234           /* Workaround for ICS limitation: we are not told the wild
4235              type when starting to examine a game.  But if we ask for
4236              the move list, the move list header will tell us */
4237             ics_getting_history = H_REQUESTED;
4238             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239             SendToICS(str);
4240         }
4241     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4242                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4243 #if ZIPPY
4244         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4245         /* [HGM] applied this also to an engine that is silently watching        */
4246         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4247             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4248             gameInfo.variant == currentlyInitializedVariant) {
4249           takeback = forwardMostMove - moveNum;
4250           for (i = 0; i < takeback; i++) {
4251             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4252             SendToProgram("undo\n", &first);
4253           }
4254         }
4255 #endif
4256
4257         forwardMostMove = moveNum;
4258         if (!pausing || currentMove > forwardMostMove)
4259           currentMove = forwardMostMove;
4260     } else {
4261         /* New part of history that is not contiguous with old part */
4262         if (pausing && gameMode == IcsExamining) {
4263             pauseExamInvalid = TRUE;
4264             forwardMostMove = pauseExamForwardMostMove;
4265             return;
4266         }
4267         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4268 #if ZIPPY
4269             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4270                 // [HGM] when we will receive the move list we now request, it will be
4271                 // fed to the engine from the first move on. So if the engine is not
4272                 // in the initial position now, bring it there.
4273                 InitChessProgram(&first, 0);
4274             }
4275 #endif
4276             ics_getting_history = H_REQUESTED;
4277             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4278             SendToICS(str);
4279         }
4280         forwardMostMove = backwardMostMove = currentMove = moveNum;
4281     }
4282
4283     /* Update the clocks */
4284     if (strchr(elapsed_time, '.')) {
4285       /* Time is in ms */
4286       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4287       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4288     } else {
4289       /* Time is in seconds */
4290       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4291       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4292     }
4293
4294
4295 #if ZIPPY
4296     if (appData.zippyPlay && newGame &&
4297         gameMode != IcsObserving && gameMode != IcsIdle &&
4298         gameMode != IcsExamining)
4299       ZippyFirstBoard(moveNum, basetime, increment);
4300 #endif
4301
4302     /* Put the move on the move list, first converting
4303        to canonical algebraic form. */
4304     if (moveNum > 0) {
4305   if (appData.debugMode) {
4306     if (appData.debugMode) { int f = forwardMostMove;
4307         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4308                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4309                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4310     }
4311     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4312     fprintf(debugFP, "moveNum = %d\n", moveNum);
4313     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4314     setbuf(debugFP, NULL);
4315   }
4316         if (moveNum <= backwardMostMove) {
4317             /* We don't know what the board looked like before
4318                this move.  Punt. */
4319           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4320             strcat(parseList[moveNum - 1], " ");
4321             strcat(parseList[moveNum - 1], elapsed_time);
4322             moveList[moveNum - 1][0] = NULLCHAR;
4323         } else if (strcmp(move_str, "none") == 0) {
4324             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4325             /* Again, we don't know what the board looked like;
4326                this is really the start of the game. */
4327             parseList[moveNum - 1][0] = NULLCHAR;
4328             moveList[moveNum - 1][0] = NULLCHAR;
4329             backwardMostMove = moveNum;
4330             startedFromSetupPosition = TRUE;
4331             fromX = fromY = toX = toY = -1;
4332         } else {
4333           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4334           //                 So we parse the long-algebraic move string in stead of the SAN move
4335           int valid; char buf[MSG_SIZ], *prom;
4336
4337           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4338                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4339           // str looks something like "Q/a1-a2"; kill the slash
4340           if(str[1] == '/')
4341             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4342           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4343           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4344                 strcat(buf, prom); // long move lacks promo specification!
4345           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4346                 if(appData.debugMode)
4347                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4348                 safeStrCpy(move_str, buf, MSG_SIZ);
4349           }
4350           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4351                                 &fromX, &fromY, &toX, &toY, &promoChar)
4352                || ParseOneMove(buf, moveNum - 1, &moveType,
4353                                 &fromX, &fromY, &toX, &toY, &promoChar);
4354           // end of long SAN patch
4355           if (valid) {
4356             (void) CoordsToAlgebraic(boards[moveNum - 1],
4357                                      PosFlags(moveNum - 1),
4358                                      fromY, fromX, toY, toX, promoChar,
4359                                      parseList[moveNum-1]);
4360             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4361               case MT_NONE:
4362               case MT_STALEMATE:
4363               default:
4364                 break;
4365               case MT_CHECK:
4366                 if(gameInfo.variant != VariantShogi)
4367                     strcat(parseList[moveNum - 1], "+");
4368                 break;
4369               case MT_CHECKMATE:
4370               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4371                 strcat(parseList[moveNum - 1], "#");
4372                 break;
4373             }
4374             strcat(parseList[moveNum - 1], " ");
4375             strcat(parseList[moveNum - 1], elapsed_time);
4376             /* currentMoveString is set as a side-effect of ParseOneMove */
4377             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4378             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4379             strcat(moveList[moveNum - 1], "\n");
4380
4381             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4382                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4383               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4384                 ChessSquare old, new = boards[moveNum][k][j];
4385                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4386                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4387                   if(old == new) continue;
4388                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4389                   else if(new == WhiteWazir || new == BlackWazir) {
4390                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4391                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4392                       else boards[moveNum][k][j] = old; // preserve type of Gold
4393                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4394                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4395               }
4396           } else {
4397             /* Move from ICS was illegal!?  Punt. */
4398             if (appData.debugMode) {
4399               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4400               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4401             }
4402             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4403             strcat(parseList[moveNum - 1], " ");
4404             strcat(parseList[moveNum - 1], elapsed_time);
4405             moveList[moveNum - 1][0] = NULLCHAR;
4406             fromX = fromY = toX = toY = -1;
4407           }
4408         }
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4411     setbuf(debugFP, NULL);
4412   }
4413
4414 #if ZIPPY
4415         /* Send move to chess program (BEFORE animating it). */
4416         if (appData.zippyPlay && !newGame && newMove &&
4417            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4418
4419             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4420                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4421                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4422                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4423                             move_str);
4424                     DisplayError(str, 0);
4425                 } else {
4426                     if (first.sendTime) {
4427                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4428                     }
4429                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4430                     if (firstMove && !bookHit) {
4431                         firstMove = FALSE;
4432                         if (first.useColors) {
4433                           SendToProgram(gameMode == IcsPlayingWhite ?
4434                                         "white\ngo\n" :
4435                                         "black\ngo\n", &first);
4436                         } else {
4437                           SendToProgram("go\n", &first);
4438                         }
4439                         first.maybeThinking = TRUE;
4440                     }
4441                 }
4442             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4443               if (moveList[moveNum - 1][0] == NULLCHAR) {
4444                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4445                 DisplayError(str, 0);
4446               } else {
4447                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4448                 SendMoveToProgram(moveNum - 1, &first);
4449               }
4450             }
4451         }
4452 #endif
4453     }
4454
4455     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4456         /* If move comes from a remote source, animate it.  If it
4457            isn't remote, it will have already been animated. */
4458         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4459             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4460         }
4461         if (!pausing && appData.highlightLastMove) {
4462             SetHighlights(fromX, fromY, toX, toY);
4463         }
4464     }
4465
4466     /* Start the clocks */
4467     whiteFlag = blackFlag = FALSE;
4468     appData.clockMode = !(basetime == 0 && increment == 0);
4469     if (ticking == 0) {
4470       ics_clock_paused = TRUE;
4471       StopClocks();
4472     } else if (ticking == 1) {
4473       ics_clock_paused = FALSE;
4474     }
4475     if (gameMode == IcsIdle ||
4476         relation == RELATION_OBSERVING_STATIC ||
4477         relation == RELATION_EXAMINING ||
4478         ics_clock_paused)
4479       DisplayBothClocks();
4480     else
4481       StartClocks();
4482
4483     /* Display opponents and material strengths */
4484     if (gameInfo.variant != VariantBughouse &&
4485         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4486         if (tinyLayout || smallLayout) {
4487             if(gameInfo.variant == VariantNormal)
4488               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4489                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4490                     basetime, increment);
4491             else
4492               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4493                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4494                     basetime, increment, (int) gameInfo.variant);
4495         } else {
4496             if(gameInfo.variant == VariantNormal)
4497               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4498                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4499                     basetime, increment);
4500             else
4501               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4502                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4503                     basetime, increment, VariantName(gameInfo.variant));
4504         }
4505         DisplayTitle(str);
4506   if (appData.debugMode) {
4507     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4508   }
4509     }
4510
4511
4512     /* Display the board */
4513     if (!pausing && !appData.noGUI) {
4514
4515       if (appData.premove)
4516           if (!gotPremove ||
4517              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4518              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4519               ClearPremoveHighlights();
4520
4521       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4522         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4523       DrawPosition(j, boards[currentMove]);
4524
4525       DisplayMove(moveNum - 1);
4526       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4527             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4528               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4529         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4530       }
4531     }
4532
4533     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4534 #if ZIPPY
4535     if(bookHit) { // [HGM] book: simulate book reply
4536         static char bookMove[MSG_SIZ]; // a bit generous?
4537
4538         programStats.nodes = programStats.depth = programStats.time =
4539         programStats.score = programStats.got_only_move = 0;
4540         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4541
4542         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4543         strcat(bookMove, bookHit);
4544         HandleMachineMove(bookMove, &first);
4545     }
4546 #endif
4547 }
4548
4549 void
4550 GetMoveListEvent()
4551 {
4552     char buf[MSG_SIZ];
4553     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4554         ics_getting_history = H_REQUESTED;
4555         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4556         SendToICS(buf);
4557     }
4558 }
4559
4560 void
4561 AnalysisPeriodicEvent(force)
4562      int force;
4563 {
4564     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4565          && !force) || !appData.periodicUpdates)
4566       return;
4567
4568     /* Send . command to Crafty to collect stats */
4569     SendToProgram(".\n", &first);
4570
4571     /* Don't send another until we get a response (this makes
4572        us stop sending to old Crafty's which don't understand
4573        the "." command (sending illegal cmds resets node count & time,
4574        which looks bad)) */
4575     programStats.ok_to_send = 0;
4576 }
4577
4578 void ics_update_width(new_width)
4579         int new_width;
4580 {
4581         ics_printf("set width %d\n", new_width);
4582 }
4583
4584 void
4585 SendMoveToProgram(moveNum, cps)
4586      int moveNum;
4587      ChessProgramState *cps;
4588 {
4589     char buf[MSG_SIZ];
4590
4591     if (cps->useUsermove) {
4592       SendToProgram("usermove ", cps);
4593     }
4594     if (cps->useSAN) {
4595       char *space;
4596       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4597         int len = space - parseList[moveNum];
4598         memcpy(buf, parseList[moveNum], len);
4599         buf[len++] = '\n';
4600         buf[len] = NULLCHAR;
4601       } else {
4602         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4603       }
4604       SendToProgram(buf, cps);
4605     } else {
4606       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4607         AlphaRank(moveList[moveNum], 4);
4608         SendToProgram(moveList[moveNum], cps);
4609         AlphaRank(moveList[moveNum], 4); // and back
4610       } else
4611       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4612        * the engine. It would be nice to have a better way to identify castle
4613        * moves here. */
4614       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4615                                                                          && cps->useOOCastle) {
4616         int fromX = moveList[moveNum][0] - AAA;
4617         int fromY = moveList[moveNum][1] - ONE;
4618         int toX = moveList[moveNum][2] - AAA;
4619         int toY = moveList[moveNum][3] - ONE;
4620         if((boards[moveNum][fromY][fromX] == WhiteKing
4621             && boards[moveNum][toY][toX] == WhiteRook)
4622            || (boards[moveNum][fromY][fromX] == BlackKing
4623                && boards[moveNum][toY][toX] == BlackRook)) {
4624           if(toX > fromX) SendToProgram("O-O\n", cps);
4625           else SendToProgram("O-O-O\n", cps);
4626         }
4627         else SendToProgram(moveList[moveNum], cps);
4628       }
4629       else SendToProgram(moveList[moveNum], cps);
4630       /* End of additions by Tord */
4631     }
4632
4633     /* [HGM] setting up the opening has brought engine in force mode! */
4634     /*       Send 'go' if we are in a mode where machine should play. */
4635     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4636         (gameMode == TwoMachinesPlay   ||
4637 #if ZIPPY
4638          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4639 #endif
4640          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4641         SendToProgram("go\n", cps);
4642   if (appData.debugMode) {
4643     fprintf(debugFP, "(extra)\n");
4644   }
4645     }
4646     setboardSpoiledMachineBlack = 0;
4647 }
4648
4649 void
4650 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4651      ChessMove moveType;
4652      int fromX, fromY, toX, toY;
4653      char promoChar;
4654 {
4655     char user_move[MSG_SIZ];
4656
4657     switch (moveType) {
4658       default:
4659         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4660                 (int)moveType, fromX, fromY, toX, toY);
4661         DisplayError(user_move + strlen("say "), 0);
4662         break;
4663       case WhiteKingSideCastle:
4664       case BlackKingSideCastle:
4665       case WhiteQueenSideCastleWild:
4666       case BlackQueenSideCastleWild:
4667       /* PUSH Fabien */
4668       case WhiteHSideCastleFR:
4669       case BlackHSideCastleFR:
4670       /* POP Fabien */
4671         snprintf(user_move, MSG_SIZ, "o-o\n");
4672         break;
4673       case WhiteQueenSideCastle:
4674       case BlackQueenSideCastle:
4675       case WhiteKingSideCastleWild:
4676       case BlackKingSideCastleWild:
4677       /* PUSH Fabien */
4678       case WhiteASideCastleFR:
4679       case BlackASideCastleFR:
4680       /* POP Fabien */
4681         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4682         break;
4683       case WhiteNonPromotion:
4684       case BlackNonPromotion:
4685         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4686         break;
4687       case WhitePromotion:
4688       case BlackPromotion:
4689         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4690           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4691                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4692                 PieceToChar(WhiteFerz));
4693         else if(gameInfo.variant == VariantGreat)
4694           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4695                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4696                 PieceToChar(WhiteMan));
4697         else
4698           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4699                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4700                 promoChar);
4701         break;
4702       case WhiteDrop:
4703       case BlackDrop:
4704       drop:
4705         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4706                  ToUpper(PieceToChar((ChessSquare) fromX)),
4707                  AAA + toX, ONE + toY);
4708         break;
4709       case IllegalMove:  /* could be a variant we don't quite understand */
4710         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4711       case NormalMove:
4712       case WhiteCapturesEnPassant:
4713       case BlackCapturesEnPassant:
4714         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4715                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4716         break;
4717     }
4718     SendToICS(user_move);
4719     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4720         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4721 }
4722
4723 void
4724 UploadGameEvent()
4725 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4726     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4727     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4728     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4729         DisplayError("You cannot do this while you are playing or observing", 0);
4730         return;
4731     }
4732     if(gameMode != IcsExamining) { // is this ever not the case?
4733         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4734
4735         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4736           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4737         } else { // on FICS we must first go to general examine mode
4738           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4739         }
4740         if(gameInfo.variant != VariantNormal) {
4741             // try figure out wild number, as xboard names are not always valid on ICS
4742             for(i=1; i<=36; i++) {
4743               snprintf(buf, MSG_SIZ, "wild/%d", i);
4744                 if(StringToVariant(buf) == gameInfo.variant) break;
4745             }
4746             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4747             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4748             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4749         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4750         SendToICS(ics_prefix);
4751         SendToICS(buf);
4752         if(startedFromSetupPosition || backwardMostMove != 0) {
4753           fen = PositionToFEN(backwardMostMove, NULL);
4754           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4755             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4756             SendToICS(buf);
4757           } else { // FICS: everything has to set by separate bsetup commands
4758             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4759             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4760             SendToICS(buf);
4761             if(!WhiteOnMove(backwardMostMove)) {
4762                 SendToICS("bsetup tomove black\n");
4763             }
4764             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4765             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4766             SendToICS(buf);
4767             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4768             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4769             SendToICS(buf);
4770             i = boards[backwardMostMove][EP_STATUS];
4771             if(i >= 0) { // set e.p.
4772               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4773                 SendToICS(buf);
4774             }
4775             bsetup++;
4776           }
4777         }
4778       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4779             SendToICS("bsetup done\n"); // switch to normal examining.
4780     }
4781     for(i = backwardMostMove; i<last; i++) {
4782         char buf[20];
4783         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4784         SendToICS(buf);
4785     }
4786     SendToICS(ics_prefix);
4787     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4788 }
4789
4790 void
4791 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4792      int rf, ff, rt, ft;
4793      char promoChar;
4794      char move[7];
4795 {
4796     if (rf == DROP_RANK) {
4797       sprintf(move, "%c@%c%c\n",
4798                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4799     } else {
4800         if (promoChar == 'x' || promoChar == NULLCHAR) {
4801           sprintf(move, "%c%c%c%c\n",
4802                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4803         } else {
4804             sprintf(move, "%c%c%c%c%c\n",
4805                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4806         }
4807     }
4808 }
4809
4810 void
4811 ProcessICSInitScript(f)
4812      FILE *f;
4813 {
4814     char buf[MSG_SIZ];
4815
4816     while (fgets(buf, MSG_SIZ, f)) {
4817         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4818     }
4819
4820     fclose(f);
4821 }
4822
4823
4824 static int lastX, lastY, selectFlag, dragging;
4825
4826 void
4827 Sweep(int step)
4828 {
4829     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4830     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4831     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4832     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4833     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4834     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4835     do {
4836         promoSweep -= step;
4837         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4838         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4839         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4840         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4841         if(!step) step = 1;
4842     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4843             appData.testLegality && (promoSweep == king ||
4844             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4845     ChangeDragPiece(promoSweep);
4846 }
4847
4848 int PromoScroll(int x, int y)
4849 {
4850   int step = 0;
4851
4852   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4853   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4854   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4855   if(!step) return FALSE;
4856   lastX = x; lastY = y;
4857   if((promoSweep < BlackPawn) == flipView) step = -step;
4858   if(step > 0) selectFlag = 1;
4859   if(!selectFlag) Sweep(step);
4860   return FALSE;
4861 }
4862
4863 void
4864 NextPiece(int step)
4865 {
4866     ChessSquare piece = boards[currentMove][toY][toX];
4867     do {
4868         pieceSweep -= step;
4869         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4870         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4871         if(!step) step = -1;
4872     } while(PieceToChar(pieceSweep) == '.');
4873     boards[currentMove][toY][toX] = pieceSweep;
4874     DrawPosition(FALSE, boards[currentMove]);
4875     boards[currentMove][toY][toX] = piece;
4876 }
4877 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4878 void
4879 AlphaRank(char *move, int n)
4880 {
4881 //    char *p = move, c; int x, y;
4882
4883     if (appData.debugMode) {
4884         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4885     }
4886
4887     if(move[1]=='*' &&
4888        move[2]>='0' && move[2]<='9' &&
4889        move[3]>='a' && move[3]<='x'    ) {
4890         move[1] = '@';
4891         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4892         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4893     } else
4894     if(move[0]>='0' && move[0]<='9' &&
4895        move[1]>='a' && move[1]<='x' &&
4896        move[2]>='0' && move[2]<='9' &&
4897        move[3]>='a' && move[3]<='x'    ) {
4898         /* input move, Shogi -> normal */
4899         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4900         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4901         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4902         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4903     } else
4904     if(move[1]=='@' &&
4905        move[3]>='0' && move[3]<='9' &&
4906        move[2]>='a' && move[2]<='x'    ) {
4907         move[1] = '*';
4908         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4909         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4910     } else
4911     if(
4912        move[0]>='a' && move[0]<='x' &&
4913        move[3]>='0' && move[3]<='9' &&
4914        move[2]>='a' && move[2]<='x'    ) {
4915          /* output move, normal -> Shogi */
4916         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4917         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4918         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4919         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4920         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4921     }
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "   out = '%s'\n", move);
4924     }
4925 }
4926
4927 char yy_textstr[8000];
4928
4929 /* Parser for moves from gnuchess, ICS, or user typein box */
4930 Boolean
4931 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4932      char *move;
4933      int moveNum;
4934      ChessMove *moveType;
4935      int *fromX, *fromY, *toX, *toY;
4936      char *promoChar;
4937 {
4938     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4939
4940     switch (*moveType) {
4941       case WhitePromotion:
4942       case BlackPromotion:
4943       case WhiteNonPromotion:
4944       case BlackNonPromotion:
4945       case NormalMove:
4946       case WhiteCapturesEnPassant:
4947       case BlackCapturesEnPassant:
4948       case WhiteKingSideCastle:
4949       case WhiteQueenSideCastle:
4950       case BlackKingSideCastle:
4951       case BlackQueenSideCastle:
4952       case WhiteKingSideCastleWild:
4953       case WhiteQueenSideCastleWild:
4954       case BlackKingSideCastleWild:
4955       case BlackQueenSideCastleWild:
4956       /* Code added by Tord: */
4957       case WhiteHSideCastleFR:
4958       case WhiteASideCastleFR:
4959       case BlackHSideCastleFR:
4960       case BlackASideCastleFR:
4961       /* End of code added by Tord */
4962       case IllegalMove:         /* bug or odd chess variant */
4963         *fromX = currentMoveString[0] - AAA;
4964         *fromY = currentMoveString[1] - ONE;
4965         *toX = currentMoveString[2] - AAA;
4966         *toY = currentMoveString[3] - ONE;
4967         *promoChar = currentMoveString[4];
4968         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4969             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4970     if (appData.debugMode) {
4971         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4972     }
4973             *fromX = *fromY = *toX = *toY = 0;
4974             return FALSE;
4975         }
4976         if (appData.testLegality) {
4977           return (*moveType != IllegalMove);
4978         } else {
4979           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4980                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4981         }
4982
4983       case WhiteDrop:
4984       case BlackDrop:
4985         *fromX = *moveType == WhiteDrop ?
4986           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4987           (int) CharToPiece(ToLower(currentMoveString[0]));
4988         *fromY = DROP_RANK;
4989         *toX = currentMoveString[2] - AAA;
4990         *toY = currentMoveString[3] - ONE;
4991         *promoChar = NULLCHAR;
4992         return TRUE;
4993
4994       case AmbiguousMove:
4995       case ImpossibleMove:
4996       case EndOfFile:
4997       case ElapsedTime:
4998       case Comment:
4999       case PGNTag:
5000       case NAG:
5001       case WhiteWins:
5002       case BlackWins:
5003       case GameIsDrawn:
5004       default:
5005     if (appData.debugMode) {
5006         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5007     }
5008         /* bug? */
5009         *fromX = *fromY = *toX = *toY = 0;
5010         *promoChar = NULLCHAR;
5011         return FALSE;
5012     }
5013 }
5014
5015
5016 void
5017 ParsePV(char *pv, Boolean storeComments)
5018 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5019   int fromX, fromY, toX, toY; char promoChar;
5020   ChessMove moveType;
5021   Boolean valid;
5022   int nr = 0;
5023
5024   endPV = forwardMostMove;
5025   do {
5026     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5027     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5028     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5029 if(appData.debugMode){
5030 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5031 }
5032     if(!valid && nr == 0 &&
5033        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5034         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5035         // Hande case where played move is different from leading PV move
5036         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5037         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5038         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5039         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5040           endPV += 2; // if position different, keep this
5041           moveList[endPV-1][0] = fromX + AAA;
5042           moveList[endPV-1][1] = fromY + ONE;
5043           moveList[endPV-1][2] = toX + AAA;
5044           moveList[endPV-1][3] = toY + ONE;
5045           parseList[endPV-1][0] = NULLCHAR;
5046           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5047         }
5048       }
5049     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5050     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5051     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5052     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5053         valid++; // allow comments in PV
5054         continue;
5055     }
5056     nr++;
5057     if(endPV+1 > framePtr) break; // no space, truncate
5058     if(!valid) break;
5059     endPV++;
5060     CopyBoard(boards[endPV], boards[endPV-1]);
5061     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5062     moveList[endPV-1][0] = fromX + AAA;
5063     moveList[endPV-1][1] = fromY + ONE;
5064     moveList[endPV-1][2] = toX + AAA;
5065     moveList[endPV-1][3] = toY + ONE;
5066     moveList[endPV-1][4] = promoChar;
5067     moveList[endPV-1][5] = NULLCHAR;
5068     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5069     if(storeComments)
5070         CoordsToAlgebraic(boards[endPV - 1],
5071                              PosFlags(endPV - 1),
5072                              fromY, fromX, toY, toX, promoChar,
5073                              parseList[endPV - 1]);
5074     else
5075         parseList[endPV-1][0] = NULLCHAR;
5076   } while(valid);
5077   currentMove = endPV;
5078   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5079   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5080                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5081   DrawPosition(TRUE, boards[currentMove]);
5082 }
5083
5084 Boolean
5085 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5086 {
5087         int startPV;
5088         char *p;
5089
5090         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5091         lastX = x; lastY = y;
5092         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5093         startPV = index;
5094         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5095         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5096         index = startPV;
5097         do{ while(buf[index] && buf[index] != '\n') index++;
5098         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5099         buf[index] = 0;
5100         ParsePV(buf+startPV, FALSE);
5101         *start = startPV; *end = index-1;
5102         return TRUE;
5103 }
5104
5105 Boolean
5106 LoadPV(int x, int y)
5107 { // called on right mouse click to load PV
5108   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5109   lastX = x; lastY = y;
5110   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5111   return TRUE;
5112 }
5113
5114 void
5115 UnLoadPV()
5116 {
5117   if(endPV < 0) return;
5118   endPV = -1;
5119   currentMove = forwardMostMove;
5120   ClearPremoveHighlights();
5121   DrawPosition(TRUE, boards[currentMove]);
5122 }
5123
5124 void
5125 MovePV(int x, int y, int h)
5126 { // step through PV based on mouse coordinates (called on mouse move)
5127   int margin = h>>3, step = 0;
5128
5129   // we must somehow check if right button is still down (might be released off board!)
5130   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5131   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5132   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5133   if(!step) return;
5134   lastX = x; lastY = y;
5135
5136   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5137   if(endPV < 0) return;
5138   if(y < margin) step = 1; else
5139   if(y > h - margin) step = -1;
5140   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5141   currentMove += step;
5142   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5143   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5144                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5145   DrawPosition(FALSE, boards[currentMove]);
5146 }
5147
5148
5149 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5150 // All positions will have equal probability, but the current method will not provide a unique
5151 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5152 #define DARK 1
5153 #define LITE 2
5154 #define ANY 3
5155
5156 int squaresLeft[4];
5157 int piecesLeft[(int)BlackPawn];
5158 int seed, nrOfShuffles;
5159
5160 void GetPositionNumber()
5161 {       // sets global variable seed
5162         int i;
5163
5164         seed = appData.defaultFrcPosition;
5165         if(seed < 0) { // randomize based on time for negative FRC position numbers
5166                 for(i=0; i<50; i++) seed += random();
5167                 seed = random() ^ random() >> 8 ^ random() << 8;
5168                 if(seed<0) seed = -seed;
5169         }
5170 }
5171
5172 int put(Board board, int pieceType, int rank, int n, int shade)
5173 // put the piece on the (n-1)-th empty squares of the given shade
5174 {
5175         int i;
5176
5177         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5178                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5179                         board[rank][i] = (ChessSquare) pieceType;
5180                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5181                         squaresLeft[ANY]--;
5182                         piecesLeft[pieceType]--;
5183                         return i;
5184                 }
5185         }
5186         return -1;
5187 }
5188
5189
5190 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5191 // calculate where the next piece goes, (any empty square), and put it there
5192 {
5193         int i;
5194
5195         i = seed % squaresLeft[shade];
5196         nrOfShuffles *= squaresLeft[shade];
5197         seed /= squaresLeft[shade];
5198         put(board, pieceType, rank, i, shade);
5199 }
5200
5201 void AddTwoPieces(Board board, int pieceType, int rank)
5202 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5203 {
5204         int i, n=squaresLeft[ANY], j=n-1, k;
5205
5206         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5207         i = seed % k;  // pick one
5208         nrOfShuffles *= k;
5209         seed /= k;
5210         while(i >= j) i -= j--;
5211         j = n - 1 - j; i += j;
5212         put(board, pieceType, rank, j, ANY);
5213         put(board, pieceType, rank, i, ANY);
5214 }
5215
5216 void SetUpShuffle(Board board, int number)
5217 {
5218         int i, p, first=1;
5219
5220         GetPositionNumber(); nrOfShuffles = 1;
5221
5222         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5223         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5224         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5225
5226         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5227
5228         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5229             p = (int) board[0][i];
5230             if(p < (int) BlackPawn) piecesLeft[p] ++;
5231             board[0][i] = EmptySquare;
5232         }
5233
5234         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5235             // shuffles restricted to allow normal castling put KRR first
5236             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5237                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5238             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5239                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5240             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5241                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5242             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5243                 put(board, WhiteRook, 0, 0, ANY);
5244             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5245         }
5246
5247         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5248             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5249             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5250                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5251                 while(piecesLeft[p] >= 2) {
5252                     AddOnePiece(board, p, 0, LITE);
5253                     AddOnePiece(board, p, 0, DARK);
5254                 }
5255                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5256             }
5257
5258         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5259             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5260             // but we leave King and Rooks for last, to possibly obey FRC restriction
5261             if(p == (int)WhiteRook) continue;
5262             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5263             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5264         }
5265
5266         // now everything is placed, except perhaps King (Unicorn) and Rooks
5267
5268         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5269             // Last King gets castling rights
5270             while(piecesLeft[(int)WhiteUnicorn]) {
5271                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5272                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5273             }
5274
5275             while(piecesLeft[(int)WhiteKing]) {
5276                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5277                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5278             }
5279
5280
5281         } else {
5282             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5283             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5284         }
5285
5286         // Only Rooks can be left; simply place them all
5287         while(piecesLeft[(int)WhiteRook]) {
5288                 i = put(board, WhiteRook, 0, 0, ANY);
5289                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5290                         if(first) {
5291                                 first=0;
5292                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5293                         }
5294                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5295                 }
5296         }
5297         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5298             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5299         }
5300
5301         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5302 }
5303
5304 int SetCharTable( char *table, const char * map )
5305 /* [HGM] moved here from winboard.c because of its general usefulness */
5306 /*       Basically a safe strcpy that uses the last character as King */
5307 {
5308     int result = FALSE; int NrPieces;
5309
5310     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5311                     && NrPieces >= 12 && !(NrPieces&1)) {
5312         int i; /* [HGM] Accept even length from 12 to 34 */
5313
5314         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5315         for( i=0; i<NrPieces/2-1; i++ ) {
5316             table[i] = map[i];
5317             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5318         }
5319         table[(int) WhiteKing]  = map[NrPieces/2-1];
5320         table[(int) BlackKing]  = map[NrPieces-1];
5321
5322         result = TRUE;
5323     }
5324
5325     return result;
5326 }
5327
5328 void Prelude(Board board)
5329 {       // [HGM] superchess: random selection of exo-pieces
5330         int i, j, k; ChessSquare p;
5331         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5332
5333         GetPositionNumber(); // use FRC position number
5334
5335         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5336             SetCharTable(pieceToChar, appData.pieceToCharTable);
5337             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5338                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5339         }
5340
5341         j = seed%4;                 seed /= 4;
5342         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5343         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5344         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5345         j = seed%3 + (seed%3 >= j); seed /= 3;
5346         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5347         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5348         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5349         j = seed%3;                 seed /= 3;
5350         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5351         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5352         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5353         j = seed%2 + (seed%2 >= j); seed /= 2;
5354         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5355         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5356         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5357         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5358         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5359         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5360         put(board, exoPieces[0],    0, 0, ANY);
5361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5362 }
5363
5364 void
5365 InitPosition(redraw)
5366      int redraw;
5367 {
5368     ChessSquare (* pieces)[BOARD_FILES];
5369     int i, j, pawnRow, overrule,
5370     oldx = gameInfo.boardWidth,
5371     oldy = gameInfo.boardHeight,
5372     oldh = gameInfo.holdingsWidth;
5373     static int oldv;
5374
5375     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5376
5377     /* [AS] Initialize pv info list [HGM] and game status */
5378     {
5379         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5380             pvInfoList[i].depth = 0;
5381             boards[i][EP_STATUS] = EP_NONE;
5382             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5383         }
5384
5385         initialRulePlies = 0; /* 50-move counter start */
5386
5387         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5388         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5389     }
5390
5391
5392     /* [HGM] logic here is completely changed. In stead of full positions */
5393     /* the initialized data only consist of the two backranks. The switch */
5394     /* selects which one we will use, which is than copied to the Board   */
5395     /* initialPosition, which for the rest is initialized by Pawns and    */
5396     /* empty squares. This initial position is then copied to boards[0],  */
5397     /* possibly after shuffling, so that it remains available.            */
5398
5399     gameInfo.holdingsWidth = 0; /* default board sizes */
5400     gameInfo.boardWidth    = 8;
5401     gameInfo.boardHeight   = 8;
5402     gameInfo.holdingsSize  = 0;
5403     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5404     for(i=0; i<BOARD_FILES-2; i++)
5405       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5406     initialPosition[EP_STATUS] = EP_NONE;
5407     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5408     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5409          SetCharTable(pieceNickName, appData.pieceNickNames);
5410     else SetCharTable(pieceNickName, "............");
5411     pieces = FIDEArray;
5412
5413     switch (gameInfo.variant) {
5414     case VariantFischeRandom:
5415       shuffleOpenings = TRUE;
5416     default:
5417       break;
5418     case VariantShatranj:
5419       pieces = ShatranjArray;
5420       nrCastlingRights = 0;
5421       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5422       break;
5423     case VariantMakruk:
5424       pieces = makrukArray;
5425       nrCastlingRights = 0;
5426       startedFromSetupPosition = TRUE;
5427       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5428       break;
5429     case VariantTwoKings:
5430       pieces = twoKingsArray;
5431       break;
5432     case VariantCapaRandom:
5433       shuffleOpenings = TRUE;
5434     case VariantCapablanca:
5435       pieces = CapablancaArray;
5436       gameInfo.boardWidth = 10;
5437       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5438       break;
5439     case VariantGothic:
5440       pieces = GothicArray;
5441       gameInfo.boardWidth = 10;
5442       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5443       break;
5444     case VariantSChess:
5445       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5446       gameInfo.holdingsSize = 7;
5447       break;
5448     case VariantJanus:
5449       pieces = JanusArray;
5450       gameInfo.boardWidth = 10;
5451       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5452       nrCastlingRights = 6;
5453         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5454         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5455         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5456         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5457         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5458         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5459       break;
5460     case VariantFalcon:
5461       pieces = FalconArray;
5462       gameInfo.boardWidth = 10;
5463       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5464       break;
5465     case VariantXiangqi:
5466       pieces = XiangqiArray;
5467       gameInfo.boardWidth  = 9;
5468       gameInfo.boardHeight = 10;
5469       nrCastlingRights = 0;
5470       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5471       break;
5472     case VariantShogi:
5473       pieces = ShogiArray;
5474       gameInfo.boardWidth  = 9;
5475       gameInfo.boardHeight = 9;
5476       gameInfo.holdingsSize = 7;
5477       nrCastlingRights = 0;
5478       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5479       break;
5480     case VariantCourier:
5481       pieces = CourierArray;
5482       gameInfo.boardWidth  = 12;
5483       nrCastlingRights = 0;
5484       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5485       break;
5486     case VariantKnightmate:
5487       pieces = KnightmateArray;
5488       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5489       break;
5490     case VariantSpartan:
5491       pieces = SpartanArray;
5492       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5493       break;
5494     case VariantFairy:
5495       pieces = fairyArray;
5496       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5497       break;
5498     case VariantGreat:
5499       pieces = GreatArray;
5500       gameInfo.boardWidth = 10;
5501       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5502       gameInfo.holdingsSize = 8;
5503       break;
5504     case VariantSuper:
5505       pieces = FIDEArray;
5506       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5507       gameInfo.holdingsSize = 8;
5508       startedFromSetupPosition = TRUE;
5509       break;
5510     case VariantCrazyhouse:
5511     case VariantBughouse:
5512       pieces = FIDEArray;
5513       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5514       gameInfo.holdingsSize = 5;
5515       break;
5516     case VariantWildCastle:
5517       pieces = FIDEArray;
5518       /* !!?shuffle with kings guaranteed to be on d or e file */
5519       shuffleOpenings = 1;
5520       break;
5521     case VariantNoCastle:
5522       pieces = FIDEArray;
5523       nrCastlingRights = 0;
5524       /* !!?unconstrained back-rank shuffle */
5525       shuffleOpenings = 1;
5526       break;
5527     }
5528
5529     overrule = 0;
5530     if(appData.NrFiles >= 0) {
5531         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5532         gameInfo.boardWidth = appData.NrFiles;
5533     }
5534     if(appData.NrRanks >= 0) {
5535         gameInfo.boardHeight = appData.NrRanks;
5536     }
5537     if(appData.holdingsSize >= 0) {
5538         i = appData.holdingsSize;
5539         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5540         gameInfo.holdingsSize = i;
5541     }
5542     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5543     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5544         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5545
5546     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5547     if(pawnRow < 1) pawnRow = 1;
5548     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5549
5550     /* User pieceToChar list overrules defaults */
5551     if(appData.pieceToCharTable != NULL)
5552         SetCharTable(pieceToChar, appData.pieceToCharTable);
5553
5554     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5555
5556         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5557             s = (ChessSquare) 0; /* account holding counts in guard band */
5558         for( i=0; i<BOARD_HEIGHT; i++ )
5559             initialPosition[i][j] = s;
5560
5561         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5562         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5563         initialPosition[pawnRow][j] = WhitePawn;
5564         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5565         if(gameInfo.variant == VariantXiangqi) {
5566             if(j&1) {
5567                 initialPosition[pawnRow][j] =
5568                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5569                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5570                    initialPosition[2][j] = WhiteCannon;
5571                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5572                 }
5573             }
5574         }
5575         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5576     }
5577     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5578
5579             j=BOARD_LEFT+1;
5580             initialPosition[1][j] = WhiteBishop;
5581             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5582             j=BOARD_RGHT-2;
5583             initialPosition[1][j] = WhiteRook;
5584             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5585     }
5586
5587     if( nrCastlingRights == -1) {
5588         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5589         /*       This sets default castling rights from none to normal corners   */
5590         /* Variants with other castling rights must set them themselves above    */
5591         nrCastlingRights = 6;
5592
5593         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5594         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5595         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5596         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5597         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5598         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5599      }
5600
5601      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5602      if(gameInfo.variant == VariantGreat) { // promotion commoners
5603         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5604         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5605         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5606         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5607      }
5608      if( gameInfo.variant == VariantSChess ) {
5609       initialPosition[1][0] = BlackMarshall;
5610       initialPosition[2][0] = BlackAngel;
5611       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5612       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5613       initialPosition[1][1] = initialPosition[2][1] = 
5614       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5615      }
5616   if (appData.debugMode) {
5617     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5618   }
5619     if(shuffleOpenings) {
5620         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5621         startedFromSetupPosition = TRUE;
5622     }
5623     if(startedFromPositionFile) {
5624       /* [HGM] loadPos: use PositionFile for every new game */
5625       CopyBoard(initialPosition, filePosition);
5626       for(i=0; i<nrCastlingRights; i++)
5627           initialRights[i] = filePosition[CASTLING][i];
5628       startedFromSetupPosition = TRUE;
5629     }
5630
5631     CopyBoard(boards[0], initialPosition);
5632
5633     if(oldx != gameInfo.boardWidth ||
5634        oldy != gameInfo.boardHeight ||
5635        oldv != gameInfo.variant ||
5636        oldh != gameInfo.holdingsWidth
5637                                          )
5638             InitDrawingSizes(-2 ,0);
5639
5640     oldv = gameInfo.variant;
5641     if (redraw)
5642       DrawPosition(TRUE, boards[currentMove]);
5643 }
5644
5645 void
5646 SendBoard(cps, moveNum)
5647      ChessProgramState *cps;
5648      int moveNum;
5649 {
5650     char message[MSG_SIZ];
5651
5652     if (cps->useSetboard) {
5653       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5654       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5655       SendToProgram(message, cps);
5656       free(fen);
5657
5658     } else {
5659       ChessSquare *bp;
5660       int i, j;
5661       /* Kludge to set black to move, avoiding the troublesome and now
5662        * deprecated "black" command.
5663        */
5664       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5665         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5666
5667       SendToProgram("edit\n", cps);
5668       SendToProgram("#\n", cps);
5669       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5670         bp = &boards[moveNum][i][BOARD_LEFT];
5671         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5672           if ((int) *bp < (int) BlackPawn) {
5673             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5674                     AAA + j, ONE + i);
5675             if(message[0] == '+' || message[0] == '~') {
5676               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5677                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5678                         AAA + j, ONE + i);
5679             }
5680             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5681                 message[1] = BOARD_RGHT   - 1 - j + '1';
5682                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5683             }
5684             SendToProgram(message, cps);
5685           }
5686         }
5687       }
5688
5689       SendToProgram("c\n", cps);
5690       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5691         bp = &boards[moveNum][i][BOARD_LEFT];
5692         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5693           if (((int) *bp != (int) EmptySquare)
5694               && ((int) *bp >= (int) BlackPawn)) {
5695             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5696                     AAA + j, ONE + i);
5697             if(message[0] == '+' || message[0] == '~') {
5698               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5699                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5700                         AAA + j, ONE + i);
5701             }
5702             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5703                 message[1] = BOARD_RGHT   - 1 - j + '1';
5704                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5705             }
5706             SendToProgram(message, cps);
5707           }
5708         }
5709       }
5710
5711       SendToProgram(".\n", cps);
5712     }
5713     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5714 }
5715
5716 ChessSquare
5717 DefaultPromoChoice(int white)
5718 {
5719     ChessSquare result;
5720     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5721         result = WhiteFerz; // no choice
5722     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5723         result= WhiteKing; // in Suicide Q is the last thing we want
5724     else if(gameInfo.variant == VariantSpartan)
5725         result = white ? WhiteQueen : WhiteAngel;
5726     else result = WhiteQueen;
5727     if(!white) result = WHITE_TO_BLACK result;
5728     return result;
5729 }
5730
5731 static int autoQueen; // [HGM] oneclick
5732
5733 int
5734 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5735 {
5736     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5737     /* [HGM] add Shogi promotions */
5738     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5739     ChessSquare piece;
5740     ChessMove moveType;
5741     Boolean premove;
5742
5743     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5744     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5745
5746     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5747       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5748         return FALSE;
5749
5750     piece = boards[currentMove][fromY][fromX];
5751     if(gameInfo.variant == VariantShogi) {
5752         promotionZoneSize = BOARD_HEIGHT/3;
5753         highestPromotingPiece = (int)WhiteFerz;
5754     } else if(gameInfo.variant == VariantMakruk) {
5755         promotionZoneSize = 3;
5756     }
5757
5758     // Treat Lance as Pawn when it is not representing Amazon
5759     if(gameInfo.variant != VariantSuper) {
5760         if(piece == WhiteLance) piece = WhitePawn; else
5761         if(piece == BlackLance) piece = BlackPawn;
5762     }
5763
5764     // next weed out all moves that do not touch the promotion zone at all
5765     if((int)piece >= BlackPawn) {
5766         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5767              return FALSE;
5768         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5769     } else {
5770         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5771            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5772     }
5773
5774     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5775
5776     // weed out mandatory Shogi promotions
5777     if(gameInfo.variant == VariantShogi) {
5778         if(piece >= BlackPawn) {
5779             if(toY == 0 && piece == BlackPawn ||
5780                toY == 0 && piece == BlackQueen ||
5781                toY <= 1 && piece == BlackKnight) {
5782                 *promoChoice = '+';
5783                 return FALSE;
5784             }
5785         } else {
5786             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5787                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5788                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5789                 *promoChoice = '+';
5790                 return FALSE;
5791             }
5792         }
5793     }
5794
5795     // weed out obviously illegal Pawn moves
5796     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5797         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5798         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5799         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5800         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5801         // note we are not allowed to test for valid (non-)capture, due to premove
5802     }
5803
5804     // we either have a choice what to promote to, or (in Shogi) whether to promote
5805     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5806         *promoChoice = PieceToChar(BlackFerz);  // no choice
5807         return FALSE;
5808     }
5809     // no sense asking what we must promote to if it is going to explode...
5810     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5811         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5812         return FALSE;
5813     }
5814     // give caller the default choice even if we will not make it
5815     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5816     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5817     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5818                            && gameInfo.variant != VariantShogi
5819                            && gameInfo.variant != VariantSuper) return FALSE;
5820     if(autoQueen) return FALSE; // predetermined
5821
5822     // suppress promotion popup on illegal moves that are not premoves
5823     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5824               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5825     if(appData.testLegality && !premove) {
5826         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5827                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5828         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5829             return FALSE;
5830     }
5831
5832     return TRUE;
5833 }
5834
5835 int
5836 InPalace(row, column)
5837      int row, column;
5838 {   /* [HGM] for Xiangqi */
5839     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5840          column < (BOARD_WIDTH + 4)/2 &&
5841          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5842     return FALSE;
5843 }
5844
5845 int
5846 PieceForSquare (x, y)
5847      int x;
5848      int y;
5849 {
5850   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5851      return -1;
5852   else
5853      return boards[currentMove][y][x];
5854 }
5855
5856 int
5857 OKToStartUserMove(x, y)
5858      int x, y;
5859 {
5860     ChessSquare from_piece;
5861     int white_piece;
5862
5863     if (matchMode) return FALSE;
5864     if (gameMode == EditPosition) return TRUE;
5865
5866     if (x >= 0 && y >= 0)
5867       from_piece = boards[currentMove][y][x];
5868     else
5869       from_piece = EmptySquare;
5870
5871     if (from_piece == EmptySquare) return FALSE;
5872
5873     white_piece = (int)from_piece >= (int)WhitePawn &&
5874       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5875
5876     switch (gameMode) {
5877       case PlayFromGameFile:
5878       case AnalyzeFile:
5879       case TwoMachinesPlay:
5880       case EndOfGame:
5881         return FALSE;
5882
5883       case IcsObserving:
5884       case IcsIdle:
5885         return FALSE;
5886
5887       case MachinePlaysWhite:
5888       case IcsPlayingBlack:
5889         if (appData.zippyPlay) return FALSE;
5890         if (white_piece) {
5891             DisplayMoveError(_("You are playing Black"));
5892             return FALSE;
5893         }
5894         break;
5895
5896       case MachinePlaysBlack:
5897       case IcsPlayingWhite:
5898         if (appData.zippyPlay) return FALSE;
5899         if (!white_piece) {
5900             DisplayMoveError(_("You are playing White"));
5901             return FALSE;
5902         }
5903         break;
5904
5905       case EditGame:
5906         if (!white_piece && WhiteOnMove(currentMove)) {
5907             DisplayMoveError(_("It is White's turn"));
5908             return FALSE;
5909         }
5910         if (white_piece && !WhiteOnMove(currentMove)) {
5911             DisplayMoveError(_("It is Black's turn"));
5912             return FALSE;
5913         }
5914         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5915             /* Editing correspondence game history */
5916             /* Could disallow this or prompt for confirmation */
5917             cmailOldMove = -1;
5918         }
5919         break;
5920
5921       case BeginningOfGame:
5922         if (appData.icsActive) return FALSE;
5923         if (!appData.noChessProgram) {
5924             if (!white_piece) {
5925                 DisplayMoveError(_("You are playing White"));
5926                 return FALSE;
5927             }
5928         }
5929         break;
5930
5931       case Training:
5932         if (!white_piece && WhiteOnMove(currentMove)) {
5933             DisplayMoveError(_("It is White's turn"));
5934             return FALSE;
5935         }
5936         if (white_piece && !WhiteOnMove(currentMove)) {
5937             DisplayMoveError(_("It is Black's turn"));
5938             return FALSE;
5939         }
5940         break;
5941
5942       default:
5943       case IcsExamining:
5944         break;
5945     }
5946     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5947         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5948         && gameMode != AnalyzeFile && gameMode != Training) {
5949         DisplayMoveError(_("Displayed position is not current"));
5950         return FALSE;
5951     }
5952     return TRUE;
5953 }
5954
5955 Boolean
5956 OnlyMove(int *x, int *y, Boolean captures) {
5957     DisambiguateClosure cl;
5958     if (appData.zippyPlay) return FALSE;
5959     switch(gameMode) {
5960       case MachinePlaysBlack:
5961       case IcsPlayingWhite:
5962       case BeginningOfGame:
5963         if(!WhiteOnMove(currentMove)) return FALSE;
5964         break;
5965       case MachinePlaysWhite:
5966       case IcsPlayingBlack:
5967         if(WhiteOnMove(currentMove)) return FALSE;
5968         break;
5969       case EditGame:
5970         break;
5971       default:
5972         return FALSE;
5973     }
5974     cl.pieceIn = EmptySquare;
5975     cl.rfIn = *y;
5976     cl.ffIn = *x;
5977     cl.rtIn = -1;
5978     cl.ftIn = -1;
5979     cl.promoCharIn = NULLCHAR;
5980     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5981     if( cl.kind == NormalMove ||
5982         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5983         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5984         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5985       fromX = cl.ff;
5986       fromY = cl.rf;
5987       *x = cl.ft;
5988       *y = cl.rt;
5989       return TRUE;
5990     }
5991     if(cl.kind != ImpossibleMove) return FALSE;
5992     cl.pieceIn = EmptySquare;
5993     cl.rfIn = -1;
5994     cl.ffIn = -1;
5995     cl.rtIn = *y;
5996     cl.ftIn = *x;
5997     cl.promoCharIn = NULLCHAR;
5998     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5999     if( cl.kind == NormalMove ||
6000         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6001         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6002         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6003       fromX = cl.ff;
6004       fromY = cl.rf;
6005       *x = cl.ft;
6006       *y = cl.rt;
6007       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6008       return TRUE;
6009     }
6010     return FALSE;
6011 }
6012
6013 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6014 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6015 int lastLoadGameUseList = FALSE;
6016 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6017 ChessMove lastLoadGameStart = EndOfFile;
6018
6019 void
6020 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6021      int fromX, fromY, toX, toY;
6022      int promoChar;
6023 {
6024     ChessMove moveType;
6025     ChessSquare pdown, pup;
6026
6027     /* Check if the user is playing in turn.  This is complicated because we
6028        let the user "pick up" a piece before it is his turn.  So the piece he
6029        tried to pick up may have been captured by the time he puts it down!
6030        Therefore we use the color the user is supposed to be playing in this
6031        test, not the color of the piece that is currently on the starting
6032        square---except in EditGame mode, where the user is playing both
6033        sides; fortunately there the capture race can't happen.  (It can
6034        now happen in IcsExamining mode, but that's just too bad.  The user
6035        will get a somewhat confusing message in that case.)
6036        */
6037
6038     switch (gameMode) {
6039       case PlayFromGameFile:
6040       case AnalyzeFile:
6041       case TwoMachinesPlay:
6042       case EndOfGame:
6043       case IcsObserving:
6044       case IcsIdle:
6045         /* We switched into a game mode where moves are not accepted,
6046            perhaps while the mouse button was down. */
6047         return;
6048
6049       case MachinePlaysWhite:
6050         /* User is moving for Black */
6051         if (WhiteOnMove(currentMove)) {
6052             DisplayMoveError(_("It is White's turn"));
6053             return;
6054         }
6055         break;
6056
6057       case MachinePlaysBlack:
6058         /* User is moving for White */
6059         if (!WhiteOnMove(currentMove)) {
6060             DisplayMoveError(_("It is Black's turn"));
6061             return;
6062         }
6063         break;
6064
6065       case EditGame:
6066       case IcsExamining:
6067       case BeginningOfGame:
6068       case AnalyzeMode:
6069       case Training:
6070         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6071         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6072             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6073             /* User is moving for Black */
6074             if (WhiteOnMove(currentMove)) {
6075                 DisplayMoveError(_("It is White's turn"));
6076                 return;
6077             }
6078         } else {
6079             /* User is moving for White */
6080             if (!WhiteOnMove(currentMove)) {
6081                 DisplayMoveError(_("It is Black's turn"));
6082                 return;
6083             }
6084         }
6085         break;
6086
6087       case IcsPlayingBlack:
6088         /* User is moving for Black */
6089         if (WhiteOnMove(currentMove)) {
6090             if (!appData.premove) {
6091                 DisplayMoveError(_("It is White's turn"));
6092             } else if (toX >= 0 && toY >= 0) {
6093                 premoveToX = toX;
6094                 premoveToY = toY;
6095                 premoveFromX = fromX;
6096                 premoveFromY = fromY;
6097                 premovePromoChar = promoChar;
6098                 gotPremove = 1;
6099                 if (appData.debugMode)
6100                     fprintf(debugFP, "Got premove: fromX %d,"
6101                             "fromY %d, toX %d, toY %d\n",
6102                             fromX, fromY, toX, toY);
6103             }
6104             return;
6105         }
6106         break;
6107
6108       case IcsPlayingWhite:
6109         /* User is moving for White */
6110         if (!WhiteOnMove(currentMove)) {
6111             if (!appData.premove) {
6112                 DisplayMoveError(_("It is Black's turn"));
6113             } else if (toX >= 0 && toY >= 0) {
6114                 premoveToX = toX;
6115                 premoveToY = toY;
6116                 premoveFromX = fromX;
6117                 premoveFromY = fromY;
6118                 premovePromoChar = promoChar;
6119                 gotPremove = 1;
6120                 if (appData.debugMode)
6121                     fprintf(debugFP, "Got premove: fromX %d,"
6122                             "fromY %d, toX %d, toY %d\n",
6123                             fromX, fromY, toX, toY);
6124             }
6125             return;
6126         }
6127         break;
6128
6129       default:
6130         break;
6131
6132       case EditPosition:
6133         /* EditPosition, empty square, or different color piece;
6134            click-click move is possible */
6135         if (toX == -2 || toY == -2) {
6136             boards[0][fromY][fromX] = EmptySquare;
6137             DrawPosition(FALSE, boards[currentMove]);
6138             return;
6139         } else if (toX >= 0 && toY >= 0) {
6140             boards[0][toY][toX] = boards[0][fromY][fromX];
6141             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6142                 if(boards[0][fromY][0] != EmptySquare) {
6143                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6144                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6145                 }
6146             } else
6147             if(fromX == BOARD_RGHT+1) {
6148                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6149                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6150                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6151                 }
6152             } else
6153             boards[0][fromY][fromX] = EmptySquare;
6154             DrawPosition(FALSE, boards[currentMove]);
6155             return;
6156         }
6157         return;
6158     }
6159
6160     if(toX < 0 || toY < 0) return;
6161     pdown = boards[currentMove][fromY][fromX];
6162     pup = boards[currentMove][toY][toX];
6163
6164     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6165     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6166          if( pup != EmptySquare ) return;
6167          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6168            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6169                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6170            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6171            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6172            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6173            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6174          fromY = DROP_RANK;
6175     }
6176
6177     /* [HGM] always test for legality, to get promotion info */
6178     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6179                                          fromY, fromX, toY, toX, promoChar);
6180     /* [HGM] but possibly ignore an IllegalMove result */
6181     if (appData.testLegality) {
6182         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6183             DisplayMoveError(_("Illegal move"));
6184             return;
6185         }
6186     }
6187
6188     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6189 }
6190
6191 /* Common tail of UserMoveEvent and DropMenuEvent */
6192 int
6193 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6194      ChessMove moveType;
6195      int fromX, fromY, toX, toY;
6196      /*char*/int promoChar;
6197 {
6198     char *bookHit = 0;
6199
6200     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6201         // [HGM] superchess: suppress promotions to non-available piece
6202         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6203         if(WhiteOnMove(currentMove)) {
6204             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6205         } else {
6206             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6207         }
6208     }
6209
6210     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6211        move type in caller when we know the move is a legal promotion */
6212     if(moveType == NormalMove && promoChar)
6213         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6214
6215     /* [HGM] <popupFix> The following if has been moved here from
6216        UserMoveEvent(). Because it seemed to belong here (why not allow
6217        piece drops in training games?), and because it can only be
6218        performed after it is known to what we promote. */
6219     if (gameMode == Training) {
6220       /* compare the move played on the board to the next move in the
6221        * game. If they match, display the move and the opponent's response.
6222        * If they don't match, display an error message.
6223        */
6224       int saveAnimate;
6225       Board testBoard;
6226       CopyBoard(testBoard, boards[currentMove]);
6227       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6228
6229       if (CompareBoards(testBoard, boards[currentMove+1])) {
6230         ForwardInner(currentMove+1);
6231
6232         /* Autoplay the opponent's response.
6233          * if appData.animate was TRUE when Training mode was entered,
6234          * the response will be animated.
6235          */
6236         saveAnimate = appData.animate;
6237         appData.animate = animateTraining;
6238         ForwardInner(currentMove+1);
6239         appData.animate = saveAnimate;
6240
6241         /* check for the end of the game */
6242         if (currentMove >= forwardMostMove) {
6243           gameMode = PlayFromGameFile;
6244           ModeHighlight();
6245           SetTrainingModeOff();
6246           DisplayInformation(_("End of game"));
6247         }
6248       } else {
6249         DisplayError(_("Incorrect move"), 0);
6250       }
6251       return 1;
6252     }
6253
6254   /* Ok, now we know that the move is good, so we can kill
6255      the previous line in Analysis Mode */
6256   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6257                                 && currentMove < forwardMostMove) {
6258     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6259     else forwardMostMove = currentMove;
6260   }
6261
6262   /* If we need the chess program but it's dead, restart it */
6263   ResurrectChessProgram();
6264
6265   /* A user move restarts a paused game*/
6266   if (pausing)
6267     PauseEvent();
6268
6269   thinkOutput[0] = NULLCHAR;
6270
6271   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6272
6273   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6274     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275     return 1;
6276   }
6277
6278   if (gameMode == BeginningOfGame) {
6279     if (appData.noChessProgram) {
6280       gameMode = EditGame;
6281       SetGameInfo();
6282     } else {
6283       char buf[MSG_SIZ];
6284       gameMode = MachinePlaysBlack;
6285       StartClocks();
6286       SetGameInfo();
6287       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6288       DisplayTitle(buf);
6289       if (first.sendName) {
6290         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6291         SendToProgram(buf, &first);
6292       }
6293       StartClocks();
6294     }
6295     ModeHighlight();
6296   }
6297
6298   /* Relay move to ICS or chess engine */
6299   if (appData.icsActive) {
6300     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6301         gameMode == IcsExamining) {
6302       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6303         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6304         SendToICS("draw ");
6305         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6306       }
6307       // also send plain move, in case ICS does not understand atomic claims
6308       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6309       ics_user_moved = 1;
6310     }
6311   } else {
6312     if (first.sendTime && (gameMode == BeginningOfGame ||
6313                            gameMode == MachinePlaysWhite ||
6314                            gameMode == MachinePlaysBlack)) {
6315       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6316     }
6317     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6318          // [HGM] book: if program might be playing, let it use book
6319         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6320         first.maybeThinking = TRUE;
6321     } else SendMoveToProgram(forwardMostMove-1, &first);
6322     if (currentMove == cmailOldMove + 1) {
6323       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6324     }
6325   }
6326
6327   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328
6329   switch (gameMode) {
6330   case EditGame:
6331     if(appData.testLegality)
6332     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6333     case MT_NONE:
6334     case MT_CHECK:
6335       break;
6336     case MT_CHECKMATE:
6337     case MT_STAINMATE:
6338       if (WhiteOnMove(currentMove)) {
6339         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6340       } else {
6341         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6342       }
6343       break;
6344     case MT_STALEMATE:
6345       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6346       break;
6347     }
6348     break;
6349
6350   case MachinePlaysBlack:
6351   case MachinePlaysWhite:
6352     /* disable certain menu options while machine is thinking */
6353     SetMachineThinkingEnables();
6354     break;
6355
6356   default:
6357     break;
6358   }
6359
6360   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6361   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6362
6363   if(bookHit) { // [HGM] book: simulate book reply
6364         static char bookMove[MSG_SIZ]; // a bit generous?
6365
6366         programStats.nodes = programStats.depth = programStats.time =
6367         programStats.score = programStats.got_only_move = 0;
6368         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6369
6370         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6371         strcat(bookMove, bookHit);
6372         HandleMachineMove(bookMove, &first);
6373   }
6374   return 1;
6375 }
6376
6377 void
6378 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6379      Board board;
6380      int flags;
6381      ChessMove kind;
6382      int rf, ff, rt, ft;
6383      VOIDSTAR closure;
6384 {
6385     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6386     Markers *m = (Markers *) closure;
6387     if(rf == fromY && ff == fromX)
6388         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6389                          || kind == WhiteCapturesEnPassant
6390                          || kind == BlackCapturesEnPassant);
6391     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6392 }
6393
6394 void
6395 MarkTargetSquares(int clear)
6396 {
6397   int x, y;
6398   if(!appData.markers || !appData.highlightDragging ||
6399      !appData.testLegality || gameMode == EditPosition) return;
6400   if(clear) {
6401     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6402   } else {
6403     int capt = 0;
6404     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6405     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6406       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6407       if(capt)
6408       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6409     }
6410   }
6411   DrawPosition(TRUE, NULL);
6412 }
6413
6414 int
6415 Explode(Board board, int fromX, int fromY, int toX, int toY)
6416 {
6417     if(gameInfo.variant == VariantAtomic &&
6418        (board[toY][toX] != EmptySquare ||                     // capture?
6419         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6420                          board[fromY][fromX] == BlackPawn   )
6421       )) {
6422         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6423         return TRUE;
6424     }
6425     return FALSE;
6426 }
6427
6428 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6429
6430 int CanPromote(ChessSquare piece, int y)
6431 {
6432         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6433         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6434         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6435            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6436            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6437                                                   gameInfo.variant == VariantMakruk) return FALSE;
6438         return (piece == BlackPawn && y == 1 ||
6439                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6440                 piece == BlackLance && y == 1 ||
6441                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6442 }
6443
6444 void LeftClick(ClickType clickType, int xPix, int yPix)
6445 {
6446     int x, y;
6447     Boolean saveAnimate;
6448     static int second = 0, promotionChoice = 0, clearFlag = 0;
6449     char promoChoice = NULLCHAR;
6450     ChessSquare piece;
6451
6452     if(appData.seekGraph && appData.icsActive && loggedOn &&
6453         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6454         SeekGraphClick(clickType, xPix, yPix, 0);
6455         return;
6456     }
6457
6458     if (clickType == Press) ErrorPopDown();
6459     MarkTargetSquares(1);
6460
6461     x = EventToSquare(xPix, BOARD_WIDTH);
6462     y = EventToSquare(yPix, BOARD_HEIGHT);
6463     if (!flipView && y >= 0) {
6464         y = BOARD_HEIGHT - 1 - y;
6465     }
6466     if (flipView && x >= 0) {
6467         x = BOARD_WIDTH - 1 - x;
6468     }
6469
6470     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6471         defaultPromoChoice = promoSweep;
6472         promoSweep = EmptySquare;   // terminate sweep
6473         promoDefaultAltered = TRUE;
6474         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6475     }
6476
6477     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6478         if(clickType == Release) return; // ignore upclick of click-click destination
6479         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6480         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6481         if(gameInfo.holdingsWidth &&
6482                 (WhiteOnMove(currentMove)
6483                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6484                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6485             // click in right holdings, for determining promotion piece
6486             ChessSquare p = boards[currentMove][y][x];
6487             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6488             if(p != EmptySquare) {
6489                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6490                 fromX = fromY = -1;
6491                 return;
6492             }
6493         }
6494         DrawPosition(FALSE, boards[currentMove]);
6495         return;
6496     }
6497
6498     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6499     if(clickType == Press
6500             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6501               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6502               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6503         return;
6504
6505     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6506         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6507
6508     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6509         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6510                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6511         defaultPromoChoice = DefaultPromoChoice(side);
6512     }
6513
6514     autoQueen = appData.alwaysPromoteToQueen;
6515
6516     if (fromX == -1) {
6517       int originalY = y;
6518       gatingPiece = EmptySquare;
6519       if (clickType != Press) {
6520         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6521             DragPieceEnd(xPix, yPix); dragging = 0;
6522             DrawPosition(FALSE, NULL);
6523         }
6524         return;
6525       }
6526       fromX = x; fromY = y;
6527       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6528          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6529          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6530             /* First square */
6531             if (OKToStartUserMove(fromX, fromY)) {
6532                 second = 0;
6533                 MarkTargetSquares(0);
6534                 DragPieceBegin(xPix, yPix); dragging = 1;
6535                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6536                     promoSweep = defaultPromoChoice;
6537                     selectFlag = 0; lastX = xPix; lastY = yPix;
6538                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6539                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6540                 }
6541                 if (appData.highlightDragging) {
6542                     SetHighlights(fromX, fromY, -1, -1);
6543                 }
6544             } else fromX = fromY = -1;
6545             return;
6546         }
6547     }
6548
6549     /* fromX != -1 */
6550     if (clickType == Press && gameMode != EditPosition) {
6551         ChessSquare fromP;
6552         ChessSquare toP;
6553         int frc;
6554
6555         // ignore off-board to clicks
6556         if(y < 0 || x < 0) return;
6557
6558         /* Check if clicking again on the same color piece */
6559         fromP = boards[currentMove][fromY][fromX];
6560         toP = boards[currentMove][y][x];
6561         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6562         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6563              WhitePawn <= toP && toP <= WhiteKing &&
6564              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6565              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6566             (BlackPawn <= fromP && fromP <= BlackKing &&
6567              BlackPawn <= toP && toP <= BlackKing &&
6568              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6569              !(fromP == BlackKing && toP == BlackRook && frc))) {
6570             /* Clicked again on same color piece -- changed his mind */
6571             second = (x == fromX && y == fromY);
6572             promoDefaultAltered = FALSE;
6573            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6574             if (appData.highlightDragging) {
6575                 SetHighlights(x, y, -1, -1);
6576             } else {
6577                 ClearHighlights();
6578             }
6579             if (OKToStartUserMove(x, y)) {
6580                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6581                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6582                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6583                  gatingPiece = boards[currentMove][fromY][fromX];
6584                 else gatingPiece = EmptySquare;
6585                 fromX = x;
6586                 fromY = y; dragging = 1;
6587                 MarkTargetSquares(0);
6588                 DragPieceBegin(xPix, yPix);
6589                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6590                     promoSweep = defaultPromoChoice;
6591                     selectFlag = 0; lastX = xPix; lastY = yPix;
6592                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6593                 }
6594             }
6595            }
6596            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6597            second = FALSE; 
6598         }
6599         // ignore clicks on holdings
6600         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6601     }
6602
6603     if (clickType == Release && x == fromX && y == fromY) {
6604         DragPieceEnd(xPix, yPix); dragging = 0;
6605         if(clearFlag) {
6606             // a deferred attempt to click-click move an empty square on top of a piece
6607             boards[currentMove][y][x] = EmptySquare;
6608             ClearHighlights();
6609             DrawPosition(FALSE, boards[currentMove]);
6610             fromX = fromY = -1; clearFlag = 0;
6611             return;
6612         }
6613         if (appData.animateDragging) {
6614             /* Undo animation damage if any */
6615             DrawPosition(FALSE, NULL);
6616         }
6617         if (second) {
6618             /* Second up/down in same square; just abort move */
6619             second = 0;
6620             fromX = fromY = -1;
6621             gatingPiece = EmptySquare;
6622             ClearHighlights();
6623             gotPremove = 0;
6624             ClearPremoveHighlights();
6625         } else {
6626             /* First upclick in same square; start click-click mode */
6627             SetHighlights(x, y, -1, -1);
6628         }
6629         return;
6630     }
6631
6632     clearFlag = 0;
6633
6634     /* we now have a different from- and (possibly off-board) to-square */
6635     /* Completed move */
6636     toX = x;
6637     toY = y;
6638     saveAnimate = appData.animate;
6639     if (clickType == Press) {
6640         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6641             // must be Edit Position mode with empty-square selected
6642             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6643             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6644             return;
6645         }
6646         /* Finish clickclick move */
6647         if (appData.animate || appData.highlightLastMove) {
6648             SetHighlights(fromX, fromY, toX, toY);
6649         } else {
6650             ClearHighlights();
6651         }
6652     } else {
6653         /* Finish drag move */
6654         if (appData.highlightLastMove) {
6655             SetHighlights(fromX, fromY, toX, toY);
6656         } else {
6657             ClearHighlights();
6658         }
6659         DragPieceEnd(xPix, yPix); dragging = 0;
6660         /* Don't animate move and drag both */
6661         appData.animate = FALSE;
6662     }
6663
6664     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6665     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6666         ChessSquare piece = boards[currentMove][fromY][fromX];
6667         if(gameMode == EditPosition && piece != EmptySquare &&
6668            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6669             int n;
6670
6671             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6672                 n = PieceToNumber(piece - (int)BlackPawn);
6673                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6674                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6675                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6676             } else
6677             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6678                 n = PieceToNumber(piece);
6679                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6680                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6681                 boards[currentMove][n][BOARD_WIDTH-2]++;
6682             }
6683             boards[currentMove][fromY][fromX] = EmptySquare;
6684         }
6685         ClearHighlights();
6686         fromX = fromY = -1;
6687         DrawPosition(TRUE, boards[currentMove]);
6688         return;
6689     }
6690
6691     // off-board moves should not be highlighted
6692     if(x < 0 || y < 0) ClearHighlights();
6693
6694     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6695
6696     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6697         SetHighlights(fromX, fromY, toX, toY);
6698         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6699             // [HGM] super: promotion to captured piece selected from holdings
6700             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6701             promotionChoice = TRUE;
6702             // kludge follows to temporarily execute move on display, without promoting yet
6703             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6704             boards[currentMove][toY][toX] = p;
6705             DrawPosition(FALSE, boards[currentMove]);
6706             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6707             boards[currentMove][toY][toX] = q;
6708             DisplayMessage("Click in holdings to choose piece", "");
6709             return;
6710         }
6711         PromotionPopUp();
6712     } else {
6713         int oldMove = currentMove;
6714         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6715         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6716         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6717         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6718            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6719             DrawPosition(TRUE, boards[currentMove]);
6720         fromX = fromY = -1;
6721     }
6722     appData.animate = saveAnimate;
6723     if (appData.animate || appData.animateDragging) {
6724         /* Undo animation damage if needed */
6725         DrawPosition(FALSE, NULL);
6726     }
6727 }
6728
6729 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6730 {   // front-end-free part taken out of PieceMenuPopup
6731     int whichMenu; int xSqr, ySqr;
6732
6733     if(seekGraphUp) { // [HGM] seekgraph
6734         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6735         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6736         return -2;
6737     }
6738
6739     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6740          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6741         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6742         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6743         if(action == Press)   {
6744             originalFlip = flipView;
6745             flipView = !flipView; // temporarily flip board to see game from partners perspective
6746             DrawPosition(TRUE, partnerBoard);
6747             DisplayMessage(partnerStatus, "");
6748             partnerUp = TRUE;
6749         } else if(action == Release) {
6750             flipView = originalFlip;
6751             DrawPosition(TRUE, boards[currentMove]);
6752             partnerUp = FALSE;
6753         }
6754         return -2;
6755     }
6756
6757     xSqr = EventToSquare(x, BOARD_WIDTH);
6758     ySqr = EventToSquare(y, BOARD_HEIGHT);
6759     if (action == Release) {
6760         if(pieceSweep != EmptySquare) {
6761             EditPositionMenuEvent(pieceSweep, toX, toY);
6762             pieceSweep = EmptySquare;
6763         } else UnLoadPV(); // [HGM] pv
6764     }
6765     if (action != Press) return -2; // return code to be ignored
6766     switch (gameMode) {
6767       case IcsExamining:
6768         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6769       case EditPosition:
6770         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6771         if (xSqr < 0 || ySqr < 0) return -1;
6772         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6773         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6774         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6775         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6776         NextPiece(0);
6777         return -2;\r
6778       case IcsObserving:
6779         if(!appData.icsEngineAnalyze) return -1;
6780       case IcsPlayingWhite:
6781       case IcsPlayingBlack:
6782         if(!appData.zippyPlay) goto noZip;
6783       case AnalyzeMode:
6784       case AnalyzeFile:
6785       case MachinePlaysWhite:
6786       case MachinePlaysBlack:
6787       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6788         if (!appData.dropMenu) {
6789           LoadPV(x, y);
6790           return 2; // flag front-end to grab mouse events
6791         }
6792         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6793            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6794       case EditGame:
6795       noZip:
6796         if (xSqr < 0 || ySqr < 0) return -1;
6797         if (!appData.dropMenu || appData.testLegality &&
6798             gameInfo.variant != VariantBughouse &&
6799             gameInfo.variant != VariantCrazyhouse) return -1;
6800         whichMenu = 1; // drop menu
6801         break;
6802       default:
6803         return -1;
6804     }
6805
6806     if (((*fromX = xSqr) < 0) ||
6807         ((*fromY = ySqr) < 0)) {
6808         *fromX = *fromY = -1;
6809         return -1;
6810     }
6811     if (flipView)
6812       *fromX = BOARD_WIDTH - 1 - *fromX;
6813     else
6814       *fromY = BOARD_HEIGHT - 1 - *fromY;
6815
6816     return whichMenu;
6817 }
6818
6819 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6820 {
6821 //    char * hint = lastHint;
6822     FrontEndProgramStats stats;
6823
6824     stats.which = cps == &first ? 0 : 1;
6825     stats.depth = cpstats->depth;
6826     stats.nodes = cpstats->nodes;
6827     stats.score = cpstats->score;
6828     stats.time = cpstats->time;
6829     stats.pv = cpstats->movelist;
6830     stats.hint = lastHint;
6831     stats.an_move_index = 0;
6832     stats.an_move_count = 0;
6833
6834     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6835         stats.hint = cpstats->move_name;
6836         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6837         stats.an_move_count = cpstats->nr_moves;
6838     }
6839
6840     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6841
6842     SetProgramStats( &stats );
6843 }
6844
6845 void
6846 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6847 {       // count all piece types
6848         int p, f, r;
6849         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6850         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6851         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6852                 p = board[r][f];
6853                 pCnt[p]++;
6854                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6855                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6856                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6857                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6858                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6859                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6860         }
6861 }
6862
6863 int
6864 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6865 {
6866         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6867         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6868
6869         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6870         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6871         if(myPawns == 2 && nMine == 3) // KPP
6872             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6873         if(myPawns == 1 && nMine == 2) // KP
6874             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6875         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6876             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6877         if(myPawns) return FALSE;
6878         if(pCnt[WhiteRook+side])
6879             return pCnt[BlackRook-side] ||
6880                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6881                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6882                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6883         if(pCnt[WhiteCannon+side]) {
6884             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6885             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6886         }
6887         if(pCnt[WhiteKnight+side])
6888             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6889         return FALSE;
6890 }
6891
6892 int
6893 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6894 {
6895         VariantClass v = gameInfo.variant;
6896
6897         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6898         if(v == VariantShatranj) return TRUE; // always winnable through baring
6899         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6900         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6901
6902         if(v == VariantXiangqi) {
6903                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6904
6905                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6906                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6907                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6908                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6909                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6910                 if(stale) // we have at least one last-rank P plus perhaps C
6911                     return majors // KPKX
6912                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6913                 else // KCA*E*
6914                     return pCnt[WhiteFerz+side] // KCAK
6915                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6916                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6917                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6918
6919         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6920                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6921
6922                 if(nMine == 1) return FALSE; // bare King
6923                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6924                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6925                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6926                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6927                 if(pCnt[WhiteKnight+side])
6928                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6929                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6930                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6931                 if(nBishops)
6932                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6933                 if(pCnt[WhiteAlfil+side])
6934                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6935                 if(pCnt[WhiteWazir+side])
6936                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6937         }
6938
6939         return TRUE;
6940 }
6941
6942 int
6943 Adjudicate(ChessProgramState *cps)
6944 {       // [HGM] some adjudications useful with buggy engines
6945         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6946         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6947         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6948         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6949         int k, count = 0; static int bare = 1;
6950         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6951         Boolean canAdjudicate = !appData.icsActive;
6952
6953         // most tests only when we understand the game, i.e. legality-checking on
6954             if( appData.testLegality )
6955             {   /* [HGM] Some more adjudications for obstinate engines */
6956                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6957                 static int moveCount = 6;
6958                 ChessMove result;
6959                 char *reason = NULL;
6960
6961                 /* Count what is on board. */
6962                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6963
6964                 /* Some material-based adjudications that have to be made before stalemate test */
6965                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6966                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6967                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6968                      if(canAdjudicate && appData.checkMates) {
6969                          if(engineOpponent)
6970                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6971                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6972                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6973                          return 1;
6974                      }
6975                 }
6976
6977                 /* Bare King in Shatranj (loses) or Losers (wins) */
6978                 if( nrW == 1 || nrB == 1) {
6979                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6980                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6981                      if(canAdjudicate && appData.checkMates) {
6982                          if(engineOpponent)
6983                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6984                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6985                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6986                          return 1;
6987                      }
6988                   } else
6989                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6990                   {    /* bare King */
6991                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6992                         if(canAdjudicate && appData.checkMates) {
6993                             /* but only adjudicate if adjudication enabled */
6994                             if(engineOpponent)
6995                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6996                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6997                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6998                             return 1;
6999                         }
7000                   }
7001                 } else bare = 1;
7002
7003
7004             // don't wait for engine to announce game end if we can judge ourselves
7005             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7006               case MT_CHECK:
7007                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7008                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7009                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7010                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7011                             checkCnt++;
7012                         if(checkCnt >= 2) {
7013                             reason = "Xboard adjudication: 3rd check";
7014                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7015                             break;
7016                         }
7017                     }
7018                 }
7019               case MT_NONE:
7020               default:
7021                 break;
7022               case MT_STALEMATE:
7023               case MT_STAINMATE:
7024                 reason = "Xboard adjudication: Stalemate";
7025                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7026                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7027                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7028                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7029                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7030                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7031                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7032                                                                         EP_CHECKMATE : EP_WINS);
7033                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7034                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7035                 }
7036                 break;
7037               case MT_CHECKMATE:
7038                 reason = "Xboard adjudication: Checkmate";
7039                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7040                 break;
7041             }
7042
7043                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7044                     case EP_STALEMATE:
7045                         result = GameIsDrawn; break;
7046                     case EP_CHECKMATE:
7047                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7048                     case EP_WINS:
7049                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7050                     default:
7051                         result = EndOfFile;
7052                 }
7053                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7054                     if(engineOpponent)
7055                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7056                     GameEnds( result, reason, GE_XBOARD );
7057                     return 1;
7058                 }
7059
7060                 /* Next absolutely insufficient mating material. */
7061                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7062                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7063                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7064
7065                      /* always flag draws, for judging claims */
7066                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7067
7068                      if(canAdjudicate && appData.materialDraws) {
7069                          /* but only adjudicate them if adjudication enabled */
7070                          if(engineOpponent) {
7071                            SendToProgram("force\n", engineOpponent); // suppress reply
7072                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7073                          }
7074                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7075                          return 1;
7076                      }
7077                 }
7078
7079                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7080                 if(gameInfo.variant == VariantXiangqi ?
7081                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7082                  : nrW + nrB == 4 &&
7083                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7084                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7085                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7086                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7087                    ) ) {
7088                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7089                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7090                           if(engineOpponent) {
7091                             SendToProgram("force\n", engineOpponent); // suppress reply
7092                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7093                           }
7094                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7095                           return 1;
7096                      }
7097                 } else moveCount = 6;
7098             }
7099         if (appData.debugMode) { int i;
7100             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7101                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7102                     appData.drawRepeats);
7103             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7104               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7105
7106         }
7107
7108         // Repetition draws and 50-move rule can be applied independently of legality testing
7109
7110                 /* Check for rep-draws */
7111                 count = 0;
7112                 for(k = forwardMostMove-2;
7113                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7114                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7115                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7116                     k-=2)
7117                 {   int rights=0;
7118                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7119                         /* compare castling rights */
7120                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7121                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7122                                 rights++; /* King lost rights, while rook still had them */
7123                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7124                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7125                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7126                                    rights++; /* but at least one rook lost them */
7127                         }
7128                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7129                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7130                                 rights++;
7131                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7132                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7133                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7134                                    rights++;
7135                         }
7136                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7137                             && appData.drawRepeats > 1) {
7138                              /* adjudicate after user-specified nr of repeats */
7139                              int result = GameIsDrawn;
7140                              char *details = "XBoard adjudication: repetition draw";
7141                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7142                                 // [HGM] xiangqi: check for forbidden perpetuals
7143                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7144                                 for(m=forwardMostMove; m>k; m-=2) {
7145                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7146                                         ourPerpetual = 0; // the current mover did not always check
7147                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7148                                         hisPerpetual = 0; // the opponent did not always check
7149                                 }
7150                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7151                                                                         ourPerpetual, hisPerpetual);
7152                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7153                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7154                                     details = "Xboard adjudication: perpetual checking";
7155                                 } else
7156                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7157                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7158                                 } else
7159                                 // Now check for perpetual chases
7160                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7161                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7162                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7163                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7164                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7165                                         details = "Xboard adjudication: perpetual chasing";
7166                                     } else
7167                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7168                                         break; // Abort repetition-checking loop.
7169                                 }
7170                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7171                              }
7172                              if(engineOpponent) {
7173                                SendToProgram("force\n", engineOpponent); // suppress reply
7174                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7175                              }
7176                              GameEnds( result, details, GE_XBOARD );
7177                              return 1;
7178                         }
7179                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7180                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7181                     }
7182                 }
7183
7184                 /* Now we test for 50-move draws. Determine ply count */
7185                 count = forwardMostMove;
7186                 /* look for last irreversble move */
7187                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7188                     count--;
7189                 /* if we hit starting position, add initial plies */
7190                 if( count == backwardMostMove )
7191                     count -= initialRulePlies;
7192                 count = forwardMostMove - count;
7193                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7194                         // adjust reversible move counter for checks in Xiangqi
7195                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7196                         if(i < backwardMostMove) i = backwardMostMove;
7197                         while(i <= forwardMostMove) {
7198                                 lastCheck = inCheck; // check evasion does not count
7199                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7200                                 if(inCheck || lastCheck) count--; // check does not count
7201                                 i++;
7202                         }
7203                 }
7204                 if( count >= 100)
7205                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7206                          /* this is used to judge if draw claims are legal */
7207                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7208                          if(engineOpponent) {
7209                            SendToProgram("force\n", engineOpponent); // suppress reply
7210                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7211                          }
7212                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7213                          return 1;
7214                 }
7215
7216                 /* if draw offer is pending, treat it as a draw claim
7217                  * when draw condition present, to allow engines a way to
7218                  * claim draws before making their move to avoid a race
7219                  * condition occurring after their move
7220                  */
7221                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7222                          char *p = NULL;
7223                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7224                              p = "Draw claim: 50-move rule";
7225                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7226                              p = "Draw claim: 3-fold repetition";
7227                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7228                              p = "Draw claim: insufficient mating material";
7229                          if( p != NULL && canAdjudicate) {
7230                              if(engineOpponent) {
7231                                SendToProgram("force\n", engineOpponent); // suppress reply
7232                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7233                              }
7234                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7235                              return 1;
7236                          }
7237                 }
7238
7239                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7240                     if(engineOpponent) {
7241                       SendToProgram("force\n", engineOpponent); // suppress reply
7242                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7243                     }
7244                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7245                     return 1;
7246                 }
7247         return 0;
7248 }
7249
7250 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7251 {   // [HGM] book: this routine intercepts moves to simulate book replies
7252     char *bookHit = NULL;
7253
7254     //first determine if the incoming move brings opponent into his book
7255     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7256         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7257     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7258     if(bookHit != NULL && !cps->bookSuspend) {
7259         // make sure opponent is not going to reply after receiving move to book position
7260         SendToProgram("force\n", cps);
7261         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7262     }
7263     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7264     // now arrange restart after book miss
7265     if(bookHit) {
7266         // after a book hit we never send 'go', and the code after the call to this routine
7267         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7268         char buf[MSG_SIZ];
7269         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7270         SendToProgram(buf, cps);
7271         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7272     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7273         SendToProgram("go\n", cps);
7274         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7275     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7276         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7277             SendToProgram("go\n", cps);
7278         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7279     }
7280     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7281 }
7282
7283 char *savedMessage;
7284 ChessProgramState *savedState;
7285 void DeferredBookMove(void)
7286 {
7287         if(savedState->lastPing != savedState->lastPong)
7288                     ScheduleDelayedEvent(DeferredBookMove, 10);
7289         else
7290         HandleMachineMove(savedMessage, savedState);
7291 }
7292
7293 void
7294 HandleMachineMove(message, cps)
7295      char *message;
7296      ChessProgramState *cps;
7297 {
7298     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7299     char realname[MSG_SIZ];
7300     int fromX, fromY, toX, toY;
7301     ChessMove moveType;
7302     char promoChar;
7303     char *p;
7304     int machineWhite;
7305     char *bookHit;
7306
7307     cps->userError = 0;
7308
7309 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7310     /*
7311      * Kludge to ignore BEL characters
7312      */
7313     while (*message == '\007') message++;
7314
7315     /*
7316      * [HGM] engine debug message: ignore lines starting with '#' character
7317      */
7318     if(cps->debug && *message == '#') return;
7319
7320     /*
7321      * Look for book output
7322      */
7323     if (cps == &first && bookRequested) {
7324         if (message[0] == '\t' || message[0] == ' ') {
7325             /* Part of the book output is here; append it */
7326             strcat(bookOutput, message);
7327             strcat(bookOutput, "  \n");
7328             return;
7329         } else if (bookOutput[0] != NULLCHAR) {
7330             /* All of book output has arrived; display it */
7331             char *p = bookOutput;
7332             while (*p != NULLCHAR) {
7333                 if (*p == '\t') *p = ' ';
7334                 p++;
7335             }
7336             DisplayInformation(bookOutput);
7337             bookRequested = FALSE;
7338             /* Fall through to parse the current output */
7339         }
7340     }
7341
7342     /*
7343      * Look for machine move.
7344      */
7345     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7346         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7347     {
7348         /* This method is only useful on engines that support ping */
7349         if (cps->lastPing != cps->lastPong) {
7350           if (gameMode == BeginningOfGame) {
7351             /* Extra move from before last new; ignore */
7352             if (appData.debugMode) {
7353                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7354             }
7355           } else {
7356             if (appData.debugMode) {
7357                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7358                         cps->which, gameMode);
7359             }
7360
7361             SendToProgram("undo\n", cps);
7362           }
7363           return;
7364         }
7365
7366         switch (gameMode) {
7367           case BeginningOfGame:
7368             /* Extra move from before last reset; ignore */
7369             if (appData.debugMode) {
7370                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7371             }
7372             return;
7373
7374           case EndOfGame:
7375           case IcsIdle:
7376           default:
7377             /* Extra move after we tried to stop.  The mode test is
7378                not a reliable way of detecting this problem, but it's
7379                the best we can do on engines that don't support ping.
7380             */
7381             if (appData.debugMode) {
7382                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7383                         cps->which, gameMode);
7384             }
7385             SendToProgram("undo\n", cps);
7386             return;
7387
7388           case MachinePlaysWhite:
7389           case IcsPlayingWhite:
7390             machineWhite = TRUE;
7391             break;
7392
7393           case MachinePlaysBlack:
7394           case IcsPlayingBlack:
7395             machineWhite = FALSE;
7396             break;
7397
7398           case TwoMachinesPlay:
7399             machineWhite = (cps->twoMachinesColor[0] == 'w');
7400             break;
7401         }
7402         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7403             if (appData.debugMode) {
7404                 fprintf(debugFP,
7405                         "Ignoring move out of turn by %s, gameMode %d"
7406                         ", forwardMost %d\n",
7407                         cps->which, gameMode, forwardMostMove);
7408             }
7409             return;
7410         }
7411
7412     if (appData.debugMode) { int f = forwardMostMove;
7413         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7414                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7415                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7416     }
7417         if(cps->alphaRank) AlphaRank(machineMove, 4);
7418         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7419                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7420             /* Machine move could not be parsed; ignore it. */
7421           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7422                     machineMove, _(cps->which));
7423             DisplayError(buf1, 0);
7424             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7425                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7426             if (gameMode == TwoMachinesPlay) {
7427               GameEnds(machineWhite ? BlackWins : WhiteWins,
7428                        buf1, GE_XBOARD);
7429             }
7430             return;
7431         }
7432
7433         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7434         /* So we have to redo legality test with true e.p. status here,  */
7435         /* to make sure an illegal e.p. capture does not slip through,   */
7436         /* to cause a forfeit on a justified illegal-move complaint      */
7437         /* of the opponent.                                              */
7438         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7439            ChessMove moveType;
7440            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7441                              fromY, fromX, toY, toX, promoChar);
7442             if (appData.debugMode) {
7443                 int i;
7444                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7445                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7446                 fprintf(debugFP, "castling rights\n");
7447             }
7448             if(moveType == IllegalMove) {
7449               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7450                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7451                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7452                            buf1, GE_XBOARD);
7453                 return;
7454            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7455            /* [HGM] Kludge to handle engines that send FRC-style castling
7456               when they shouldn't (like TSCP-Gothic) */
7457            switch(moveType) {
7458              case WhiteASideCastleFR:
7459              case BlackASideCastleFR:
7460                toX+=2;
7461                currentMoveString[2]++;
7462                break;
7463              case WhiteHSideCastleFR:
7464              case BlackHSideCastleFR:
7465                toX--;
7466                currentMoveString[2]--;
7467                break;
7468              default: ; // nothing to do, but suppresses warning of pedantic compilers
7469            }
7470         }
7471         hintRequested = FALSE;
7472         lastHint[0] = NULLCHAR;
7473         bookRequested = FALSE;
7474         /* Program may be pondering now */
7475         cps->maybeThinking = TRUE;
7476         if (cps->sendTime == 2) cps->sendTime = 1;
7477         if (cps->offeredDraw) cps->offeredDraw--;
7478
7479         /* [AS] Save move info*/
7480         pvInfoList[ forwardMostMove ].score = programStats.score;
7481         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7482         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7483
7484         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7485
7486         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7487         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7488             int count = 0;
7489
7490             while( count < adjudicateLossPlies ) {
7491                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7492
7493                 if( count & 1 ) {
7494                     score = -score; /* Flip score for winning side */
7495                 }
7496
7497                 if( score > adjudicateLossThreshold ) {
7498                     break;
7499                 }
7500
7501                 count++;
7502             }
7503
7504             if( count >= adjudicateLossPlies ) {
7505                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7506
7507                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7508                     "Xboard adjudication",
7509                     GE_XBOARD );
7510
7511                 return;
7512             }
7513         }
7514
7515         if(Adjudicate(cps)) {
7516             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7517             return; // [HGM] adjudicate: for all automatic game ends
7518         }
7519
7520 #if ZIPPY
7521         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7522             first.initDone) {
7523           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7524                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7525                 SendToICS("draw ");
7526                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7527           }
7528           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7529           ics_user_moved = 1;
7530           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7531                 char buf[3*MSG_SIZ];
7532
7533                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7534                         programStats.score / 100.,
7535                         programStats.depth,
7536                         programStats.time / 100.,
7537                         (unsigned int)programStats.nodes,
7538                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7539                         programStats.movelist);
7540                 SendToICS(buf);
7541 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7542           }
7543         }
7544 #endif
7545
7546         /* [AS] Clear stats for next move */
7547         ClearProgramStats();
7548         thinkOutput[0] = NULLCHAR;
7549         hiddenThinkOutputState = 0;
7550
7551         bookHit = NULL;
7552         if (gameMode == TwoMachinesPlay) {
7553             /* [HGM] relaying draw offers moved to after reception of move */
7554             /* and interpreting offer as claim if it brings draw condition */
7555             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7556                 SendToProgram("draw\n", cps->other);
7557             }
7558             if (cps->other->sendTime) {
7559                 SendTimeRemaining(cps->other,
7560                                   cps->other->twoMachinesColor[0] == 'w');
7561             }
7562             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7563             if (firstMove && !bookHit) {
7564                 firstMove = FALSE;
7565                 if (cps->other->useColors) {
7566                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7567                 }
7568                 SendToProgram("go\n", cps->other);
7569             }
7570             cps->other->maybeThinking = TRUE;
7571         }
7572
7573         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7574
7575         if (!pausing && appData.ringBellAfterMoves) {
7576             RingBell();
7577         }
7578
7579         /*
7580          * Reenable menu items that were disabled while
7581          * machine was thinking
7582          */
7583         if (gameMode != TwoMachinesPlay)
7584             SetUserThinkingEnables();
7585
7586         // [HGM] book: after book hit opponent has received move and is now in force mode
7587         // force the book reply into it, and then fake that it outputted this move by jumping
7588         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7589         if(bookHit) {
7590                 static char bookMove[MSG_SIZ]; // a bit generous?
7591
7592                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7593                 strcat(bookMove, bookHit);
7594                 message = bookMove;
7595                 cps = cps->other;
7596                 programStats.nodes = programStats.depth = programStats.time =
7597                 programStats.score = programStats.got_only_move = 0;
7598                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7599
7600                 if(cps->lastPing != cps->lastPong) {
7601                     savedMessage = message; // args for deferred call
7602                     savedState = cps;
7603                     ScheduleDelayedEvent(DeferredBookMove, 10);
7604                     return;
7605                 }
7606                 goto FakeBookMove;
7607         }
7608
7609         return;
7610     }
7611
7612     /* Set special modes for chess engines.  Later something general
7613      *  could be added here; for now there is just one kludge feature,
7614      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7615      *  when "xboard" is given as an interactive command.
7616      */
7617     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7618         cps->useSigint = FALSE;
7619         cps->useSigterm = FALSE;
7620     }
7621     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7622       ParseFeatures(message+8, cps);
7623       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7624     }
7625
7626     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7627       int dummy, s=6; char buf[MSG_SIZ];
7628       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7629       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7630       ParseFEN(boards[0], &dummy, message+s);
7631       DrawPosition(TRUE, boards[0]);
7632       startedFromSetupPosition = TRUE;
7633       return;
7634     }
7635     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7636      * want this, I was asked to put it in, and obliged.
7637      */
7638     if (!strncmp(message, "setboard ", 9)) {
7639         Board initial_position;
7640
7641         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7642
7643         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7644             DisplayError(_("Bad FEN received from engine"), 0);
7645             return ;
7646         } else {
7647            Reset(TRUE, FALSE);
7648            CopyBoard(boards[0], initial_position);
7649            initialRulePlies = FENrulePlies;
7650            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7651            else gameMode = MachinePlaysBlack;
7652            DrawPosition(FALSE, boards[currentMove]);
7653         }
7654         return;
7655     }
7656
7657     /*
7658      * Look for communication commands
7659      */
7660     if (!strncmp(message, "telluser ", 9)) {
7661         if(message[9] == '\\' && message[10] == '\\')
7662             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7663         DisplayNote(message + 9);
7664         return;
7665     }
7666     if (!strncmp(message, "tellusererror ", 14)) {
7667         cps->userError = 1;
7668         if(message[14] == '\\' && message[15] == '\\')
7669             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7670         DisplayError(message + 14, 0);
7671         return;
7672     }
7673     if (!strncmp(message, "tellopponent ", 13)) {
7674       if (appData.icsActive) {
7675         if (loggedOn) {
7676           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7677           SendToICS(buf1);
7678         }
7679       } else {
7680         DisplayNote(message + 13);
7681       }
7682       return;
7683     }
7684     if (!strncmp(message, "tellothers ", 11)) {
7685       if (appData.icsActive) {
7686         if (loggedOn) {
7687           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7688           SendToICS(buf1);
7689         }
7690       }
7691       return;
7692     }
7693     if (!strncmp(message, "tellall ", 8)) {
7694       if (appData.icsActive) {
7695         if (loggedOn) {
7696           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7697           SendToICS(buf1);
7698         }
7699       } else {
7700         DisplayNote(message + 8);
7701       }
7702       return;
7703     }
7704     if (strncmp(message, "warning", 7) == 0) {
7705         /* Undocumented feature, use tellusererror in new code */
7706         DisplayError(message, 0);
7707         return;
7708     }
7709     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7710         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7711         strcat(realname, " query");
7712         AskQuestion(realname, buf2, buf1, cps->pr);
7713         return;
7714     }
7715     /* Commands from the engine directly to ICS.  We don't allow these to be
7716      *  sent until we are logged on. Crafty kibitzes have been known to
7717      *  interfere with the login process.
7718      */
7719     if (loggedOn) {
7720         if (!strncmp(message, "tellics ", 8)) {
7721             SendToICS(message + 8);
7722             SendToICS("\n");
7723             return;
7724         }
7725         if (!strncmp(message, "tellicsnoalias ", 15)) {
7726             SendToICS(ics_prefix);
7727             SendToICS(message + 15);
7728             SendToICS("\n");
7729             return;
7730         }
7731         /* The following are for backward compatibility only */
7732         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7733             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7734             SendToICS(ics_prefix);
7735             SendToICS(message);
7736             SendToICS("\n");
7737             return;
7738         }
7739     }
7740     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7741         return;
7742     }
7743     /*
7744      * If the move is illegal, cancel it and redraw the board.
7745      * Also deal with other error cases.  Matching is rather loose
7746      * here to accommodate engines written before the spec.
7747      */
7748     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7749         strncmp(message, "Error", 5) == 0) {
7750         if (StrStr(message, "name") ||
7751             StrStr(message, "rating") || StrStr(message, "?") ||
7752             StrStr(message, "result") || StrStr(message, "board") ||
7753             StrStr(message, "bk") || StrStr(message, "computer") ||
7754             StrStr(message, "variant") || StrStr(message, "hint") ||
7755             StrStr(message, "random") || StrStr(message, "depth") ||
7756             StrStr(message, "accepted")) {
7757             return;
7758         }
7759         if (StrStr(message, "protover")) {
7760           /* Program is responding to input, so it's apparently done
7761              initializing, and this error message indicates it is
7762              protocol version 1.  So we don't need to wait any longer
7763              for it to initialize and send feature commands. */
7764           FeatureDone(cps, 1);
7765           cps->protocolVersion = 1;
7766           return;
7767         }
7768         cps->maybeThinking = FALSE;
7769
7770         if (StrStr(message, "draw")) {
7771             /* Program doesn't have "draw" command */
7772             cps->sendDrawOffers = 0;
7773             return;
7774         }
7775         if (cps->sendTime != 1 &&
7776             (StrStr(message, "time") || StrStr(message, "otim"))) {
7777           /* Program apparently doesn't have "time" or "otim" command */
7778           cps->sendTime = 0;
7779           return;
7780         }
7781         if (StrStr(message, "analyze")) {
7782             cps->analysisSupport = FALSE;
7783             cps->analyzing = FALSE;
7784             Reset(FALSE, TRUE);
7785             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7786             DisplayError(buf2, 0);
7787             return;
7788         }
7789         if (StrStr(message, "(no matching move)st")) {
7790           /* Special kludge for GNU Chess 4 only */
7791           cps->stKludge = TRUE;
7792           SendTimeControl(cps, movesPerSession, timeControl,
7793                           timeIncrement, appData.searchDepth,
7794                           searchTime);
7795           return;
7796         }
7797         if (StrStr(message, "(no matching move)sd")) {
7798           /* Special kludge for GNU Chess 4 only */
7799           cps->sdKludge = TRUE;
7800           SendTimeControl(cps, movesPerSession, timeControl,
7801                           timeIncrement, appData.searchDepth,
7802                           searchTime);
7803           return;
7804         }
7805         if (!StrStr(message, "llegal")) {
7806             return;
7807         }
7808         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7809             gameMode == IcsIdle) return;
7810         if (forwardMostMove <= backwardMostMove) return;
7811         if (pausing) PauseEvent();
7812       if(appData.forceIllegal) {
7813             // [HGM] illegal: machine refused move; force position after move into it
7814           SendToProgram("force\n", cps);
7815           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7816                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7817                 // when black is to move, while there might be nothing on a2 or black
7818                 // might already have the move. So send the board as if white has the move.
7819                 // But first we must change the stm of the engine, as it refused the last move
7820                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7821                 if(WhiteOnMove(forwardMostMove)) {
7822                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7823                     SendBoard(cps, forwardMostMove); // kludgeless board
7824                 } else {
7825                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7826                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7827                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7828                 }
7829           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7830             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7831                  gameMode == TwoMachinesPlay)
7832               SendToProgram("go\n", cps);
7833             return;
7834       } else
7835         if (gameMode == PlayFromGameFile) {
7836             /* Stop reading this game file */
7837             gameMode = EditGame;
7838             ModeHighlight();
7839         }
7840         /* [HGM] illegal-move claim should forfeit game when Xboard */
7841         /* only passes fully legal moves                            */
7842         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7843             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7844                                 "False illegal-move claim", GE_XBOARD );
7845             return; // do not take back move we tested as valid
7846         }
7847         currentMove = forwardMostMove-1;
7848         DisplayMove(currentMove-1); /* before DisplayMoveError */
7849         SwitchClocks(forwardMostMove-1); // [HGM] race
7850         DisplayBothClocks();
7851         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7852                 parseList[currentMove], _(cps->which));
7853         DisplayMoveError(buf1);
7854         DrawPosition(FALSE, boards[currentMove]);
7855         return;
7856     }
7857     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7858         /* Program has a broken "time" command that
7859            outputs a string not ending in newline.
7860            Don't use it. */
7861         cps->sendTime = 0;
7862     }
7863
7864     /*
7865      * If chess program startup fails, exit with an error message.
7866      * Attempts to recover here are futile.
7867      */
7868     if ((StrStr(message, "unknown host") != NULL)
7869         || (StrStr(message, "No remote directory") != NULL)
7870         || (StrStr(message, "not found") != NULL)
7871         || (StrStr(message, "No such file") != NULL)
7872         || (StrStr(message, "can't alloc") != NULL)
7873         || (StrStr(message, "Permission denied") != NULL)) {
7874
7875         cps->maybeThinking = FALSE;
7876         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7877                 _(cps->which), cps->program, cps->host, message);
7878         RemoveInputSource(cps->isr);
7879         DisplayFatalError(buf1, 0, 1);
7880         return;
7881     }
7882
7883     /*
7884      * Look for hint output
7885      */
7886     if (sscanf(message, "Hint: %s", buf1) == 1) {
7887         if (cps == &first && hintRequested) {
7888             hintRequested = FALSE;
7889             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7890                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7891                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7892                                     PosFlags(forwardMostMove),
7893                                     fromY, fromX, toY, toX, promoChar, buf1);
7894                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7895                 DisplayInformation(buf2);
7896             } else {
7897                 /* Hint move could not be parsed!? */
7898               snprintf(buf2, sizeof(buf2),
7899                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7900                         buf1, _(cps->which));
7901                 DisplayError(buf2, 0);
7902             }
7903         } else {
7904           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7905         }
7906         return;
7907     }
7908
7909     /*
7910      * Ignore other messages if game is not in progress
7911      */
7912     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7913         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7914
7915     /*
7916      * look for win, lose, draw, or draw offer
7917      */
7918     if (strncmp(message, "1-0", 3) == 0) {
7919         char *p, *q, *r = "";
7920         p = strchr(message, '{');
7921         if (p) {
7922             q = strchr(p, '}');
7923             if (q) {
7924                 *q = NULLCHAR;
7925                 r = p + 1;
7926             }
7927         }
7928         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7929         return;
7930     } else if (strncmp(message, "0-1", 3) == 0) {
7931         char *p, *q, *r = "";
7932         p = strchr(message, '{');
7933         if (p) {
7934             q = strchr(p, '}');
7935             if (q) {
7936                 *q = NULLCHAR;
7937                 r = p + 1;
7938             }
7939         }
7940         /* Kludge for Arasan 4.1 bug */
7941         if (strcmp(r, "Black resigns") == 0) {
7942             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7943             return;
7944         }
7945         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7946         return;
7947     } else if (strncmp(message, "1/2", 3) == 0) {
7948         char *p, *q, *r = "";
7949         p = strchr(message, '{');
7950         if (p) {
7951             q = strchr(p, '}');
7952             if (q) {
7953                 *q = NULLCHAR;
7954                 r = p + 1;
7955             }
7956         }
7957
7958         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7959         return;
7960
7961     } else if (strncmp(message, "White resign", 12) == 0) {
7962         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7963         return;
7964     } else if (strncmp(message, "Black resign", 12) == 0) {
7965         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7966         return;
7967     } else if (strncmp(message, "White matches", 13) == 0 ||
7968                strncmp(message, "Black matches", 13) == 0   ) {
7969         /* [HGM] ignore GNUShogi noises */
7970         return;
7971     } else if (strncmp(message, "White", 5) == 0 &&
7972                message[5] != '(' &&
7973                StrStr(message, "Black") == NULL) {
7974         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7975         return;
7976     } else if (strncmp(message, "Black", 5) == 0 &&
7977                message[5] != '(') {
7978         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7979         return;
7980     } else if (strcmp(message, "resign") == 0 ||
7981                strcmp(message, "computer resigns") == 0) {
7982         switch (gameMode) {
7983           case MachinePlaysBlack:
7984           case IcsPlayingBlack:
7985             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7986             break;
7987           case MachinePlaysWhite:
7988           case IcsPlayingWhite:
7989             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7990             break;
7991           case TwoMachinesPlay:
7992             if (cps->twoMachinesColor[0] == 'w')
7993               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7994             else
7995               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7996             break;
7997           default:
7998             /* can't happen */
7999             break;
8000         }
8001         return;
8002     } else if (strncmp(message, "opponent mates", 14) == 0) {
8003         switch (gameMode) {
8004           case MachinePlaysBlack:
8005           case IcsPlayingBlack:
8006             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8007             break;
8008           case MachinePlaysWhite:
8009           case IcsPlayingWhite:
8010             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8011             break;
8012           case TwoMachinesPlay:
8013             if (cps->twoMachinesColor[0] == 'w')
8014               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8015             else
8016               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8017             break;
8018           default:
8019             /* can't happen */
8020             break;
8021         }
8022         return;
8023     } else if (strncmp(message, "computer mates", 14) == 0) {
8024         switch (gameMode) {
8025           case MachinePlaysBlack:
8026           case IcsPlayingBlack:
8027             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8028             break;
8029           case MachinePlaysWhite:
8030           case IcsPlayingWhite:
8031             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8032             break;
8033           case TwoMachinesPlay:
8034             if (cps->twoMachinesColor[0] == 'w')
8035               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8036             else
8037               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8038             break;
8039           default:
8040             /* can't happen */
8041             break;
8042         }
8043         return;
8044     } else if (strncmp(message, "checkmate", 9) == 0) {
8045         if (WhiteOnMove(forwardMostMove)) {
8046             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8047         } else {
8048             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8049         }
8050         return;
8051     } else if (strstr(message, "Draw") != NULL ||
8052                strstr(message, "game is a draw") != NULL) {
8053         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8054         return;
8055     } else if (strstr(message, "offer") != NULL &&
8056                strstr(message, "draw") != NULL) {
8057 #if ZIPPY
8058         if (appData.zippyPlay && first.initDone) {
8059             /* Relay offer to ICS */
8060             SendToICS(ics_prefix);
8061             SendToICS("draw\n");
8062         }
8063 #endif
8064         cps->offeredDraw = 2; /* valid until this engine moves twice */
8065         if (gameMode == TwoMachinesPlay) {
8066             if (cps->other->offeredDraw) {
8067                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8068             /* [HGM] in two-machine mode we delay relaying draw offer      */
8069             /* until after we also have move, to see if it is really claim */
8070             }
8071         } else if (gameMode == MachinePlaysWhite ||
8072                    gameMode == MachinePlaysBlack) {
8073           if (userOfferedDraw) {
8074             DisplayInformation(_("Machine accepts your draw offer"));
8075             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8076           } else {
8077             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8078           }
8079         }
8080     }
8081
8082
8083     /*
8084      * Look for thinking output
8085      */
8086     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8087           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8088                                 ) {
8089         int plylev, mvleft, mvtot, curscore, time;
8090         char mvname[MOVE_LEN];
8091         u64 nodes; // [DM]
8092         char plyext;
8093         int ignore = FALSE;
8094         int prefixHint = FALSE;
8095         mvname[0] = NULLCHAR;
8096
8097         switch (gameMode) {
8098           case MachinePlaysBlack:
8099           case IcsPlayingBlack:
8100             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8101             break;
8102           case MachinePlaysWhite:
8103           case IcsPlayingWhite:
8104             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8105             break;
8106           case AnalyzeMode:
8107           case AnalyzeFile:
8108             break;
8109           case IcsObserving: /* [DM] icsEngineAnalyze */
8110             if (!appData.icsEngineAnalyze) ignore = TRUE;
8111             break;
8112           case TwoMachinesPlay:
8113             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8114                 ignore = TRUE;
8115             }
8116             break;
8117           default:
8118             ignore = TRUE;
8119             break;
8120         }
8121
8122         if (!ignore) {
8123             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8124             buf1[0] = NULLCHAR;
8125             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8126                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8127
8128                 if (plyext != ' ' && plyext != '\t') {
8129                     time *= 100;
8130                 }
8131
8132                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8133                 if( cps->scoreIsAbsolute &&
8134                     ( gameMode == MachinePlaysBlack ||
8135                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8136                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8137                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8138                      !WhiteOnMove(currentMove)
8139                     ) )
8140                 {
8141                     curscore = -curscore;
8142                 }
8143
8144
8145                 tempStats.depth = plylev;
8146                 tempStats.nodes = nodes;
8147                 tempStats.time = time;
8148                 tempStats.score = curscore;
8149                 tempStats.got_only_move = 0;
8150
8151                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8152                         int ticklen;
8153
8154                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8155                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8156                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8157                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8158                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8159                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8160                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8161                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8162                 }
8163
8164                 /* Buffer overflow protection */
8165                 if (buf1[0] != NULLCHAR) {
8166                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8167                         && appData.debugMode) {
8168                         fprintf(debugFP,
8169                                 "PV is too long; using the first %u bytes.\n",
8170                                 (unsigned) sizeof(tempStats.movelist) - 1);
8171                     }
8172
8173                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8174                 } else {
8175                     sprintf(tempStats.movelist, " no PV\n");
8176                 }
8177
8178                 if (tempStats.seen_stat) {
8179                     tempStats.ok_to_send = 1;
8180                 }
8181
8182                 if (strchr(tempStats.movelist, '(') != NULL) {
8183                     tempStats.line_is_book = 1;
8184                     tempStats.nr_moves = 0;
8185                     tempStats.moves_left = 0;
8186                 } else {
8187                     tempStats.line_is_book = 0;
8188                 }
8189
8190                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8191                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8192
8193                 SendProgramStatsToFrontend( cps, &tempStats );
8194
8195                 /*
8196                     [AS] Protect the thinkOutput buffer from overflow... this
8197                     is only useful if buf1 hasn't overflowed first!
8198                 */
8199                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8200                          plylev,
8201                          (gameMode == TwoMachinesPlay ?
8202                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8203                          ((double) curscore) / 100.0,
8204                          prefixHint ? lastHint : "",
8205                          prefixHint ? " " : "" );
8206
8207                 if( buf1[0] != NULLCHAR ) {
8208                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8209
8210                     if( strlen(buf1) > max_len ) {
8211                         if( appData.debugMode) {
8212                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8213                         }
8214                         buf1[max_len+1] = '\0';
8215                     }
8216
8217                     strcat( thinkOutput, buf1 );
8218                 }
8219
8220                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8221                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8222                     DisplayMove(currentMove - 1);
8223                 }
8224                 return;
8225
8226             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8227                 /* crafty (9.25+) says "(only move) <move>"
8228                  * if there is only 1 legal move
8229                  */
8230                 sscanf(p, "(only move) %s", buf1);
8231                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8232                 sprintf(programStats.movelist, "%s (only move)", buf1);
8233                 programStats.depth = 1;
8234                 programStats.nr_moves = 1;
8235                 programStats.moves_left = 1;
8236                 programStats.nodes = 1;
8237                 programStats.time = 1;
8238                 programStats.got_only_move = 1;
8239
8240                 /* Not really, but we also use this member to
8241                    mean "line isn't going to change" (Crafty
8242                    isn't searching, so stats won't change) */
8243                 programStats.line_is_book = 1;
8244
8245                 SendProgramStatsToFrontend( cps, &programStats );
8246
8247                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8248                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8249                     DisplayMove(currentMove - 1);
8250                 }
8251                 return;
8252             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8253                               &time, &nodes, &plylev, &mvleft,
8254                               &mvtot, mvname) >= 5) {
8255                 /* The stat01: line is from Crafty (9.29+) in response
8256                    to the "." command */
8257                 programStats.seen_stat = 1;
8258                 cps->maybeThinking = TRUE;
8259
8260                 if (programStats.got_only_move || !appData.periodicUpdates)
8261                   return;
8262
8263                 programStats.depth = plylev;
8264                 programStats.time = time;
8265                 programStats.nodes = nodes;
8266                 programStats.moves_left = mvleft;
8267                 programStats.nr_moves = mvtot;
8268                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8269                 programStats.ok_to_send = 1;
8270                 programStats.movelist[0] = '\0';
8271
8272                 SendProgramStatsToFrontend( cps, &programStats );
8273
8274                 return;
8275
8276             } else if (strncmp(message,"++",2) == 0) {
8277                 /* Crafty 9.29+ outputs this */
8278                 programStats.got_fail = 2;
8279                 return;
8280
8281             } else if (strncmp(message,"--",2) == 0) {
8282                 /* Crafty 9.29+ outputs this */
8283                 programStats.got_fail = 1;
8284                 return;
8285
8286             } else if (thinkOutput[0] != NULLCHAR &&
8287                        strncmp(message, "    ", 4) == 0) {
8288                 unsigned message_len;
8289
8290                 p = message;
8291                 while (*p && *p == ' ') p++;
8292
8293                 message_len = strlen( p );
8294
8295                 /* [AS] Avoid buffer overflow */
8296                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8297                     strcat(thinkOutput, " ");
8298                     strcat(thinkOutput, p);
8299                 }
8300
8301                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8302                     strcat(programStats.movelist, " ");
8303                     strcat(programStats.movelist, p);
8304                 }
8305
8306                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8307                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8308                     DisplayMove(currentMove - 1);
8309                 }
8310                 return;
8311             }
8312         }
8313         else {
8314             buf1[0] = NULLCHAR;
8315
8316             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8317                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8318             {
8319                 ChessProgramStats cpstats;
8320
8321                 if (plyext != ' ' && plyext != '\t') {
8322                     time *= 100;
8323                 }
8324
8325                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8326                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8327                     curscore = -curscore;
8328                 }
8329
8330                 cpstats.depth = plylev;
8331                 cpstats.nodes = nodes;
8332                 cpstats.time = time;
8333                 cpstats.score = curscore;
8334                 cpstats.got_only_move = 0;
8335                 cpstats.movelist[0] = '\0';
8336
8337                 if (buf1[0] != NULLCHAR) {
8338                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8339                 }
8340
8341                 cpstats.ok_to_send = 0;
8342                 cpstats.line_is_book = 0;
8343                 cpstats.nr_moves = 0;
8344                 cpstats.moves_left = 0;
8345
8346                 SendProgramStatsToFrontend( cps, &cpstats );
8347             }
8348         }
8349     }
8350 }
8351
8352
8353 /* Parse a game score from the character string "game", and
8354    record it as the history of the current game.  The game
8355    score is NOT assumed to start from the standard position.
8356    The display is not updated in any way.
8357    */
8358 void
8359 ParseGameHistory(game)
8360      char *game;
8361 {
8362     ChessMove moveType;
8363     int fromX, fromY, toX, toY, boardIndex;
8364     char promoChar;
8365     char *p, *q;
8366     char buf[MSG_SIZ];
8367
8368     if (appData.debugMode)
8369       fprintf(debugFP, "Parsing game history: %s\n", game);
8370
8371     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8372     gameInfo.site = StrSave(appData.icsHost);
8373     gameInfo.date = PGNDate();
8374     gameInfo.round = StrSave("-");
8375
8376     /* Parse out names of players */
8377     while (*game == ' ') game++;
8378     p = buf;
8379     while (*game != ' ') *p++ = *game++;
8380     *p = NULLCHAR;
8381     gameInfo.white = StrSave(buf);
8382     while (*game == ' ') game++;
8383     p = buf;
8384     while (*game != ' ' && *game != '\n') *p++ = *game++;
8385     *p = NULLCHAR;
8386     gameInfo.black = StrSave(buf);
8387
8388     /* Parse moves */
8389     boardIndex = blackPlaysFirst ? 1 : 0;
8390     yynewstr(game);
8391     for (;;) {
8392         yyboardindex = boardIndex;
8393         moveType = (ChessMove) Myylex();
8394         switch (moveType) {
8395           case IllegalMove:             /* maybe suicide chess, etc. */
8396   if (appData.debugMode) {
8397     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8398     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8399     setbuf(debugFP, NULL);
8400   }
8401           case WhitePromotion:
8402           case BlackPromotion:
8403           case WhiteNonPromotion:
8404           case BlackNonPromotion:
8405           case NormalMove:
8406           case WhiteCapturesEnPassant:
8407           case BlackCapturesEnPassant:
8408           case WhiteKingSideCastle:
8409           case WhiteQueenSideCastle:
8410           case BlackKingSideCastle:
8411           case BlackQueenSideCastle:
8412           case WhiteKingSideCastleWild:
8413           case WhiteQueenSideCastleWild:
8414           case BlackKingSideCastleWild:
8415           case BlackQueenSideCastleWild:
8416           /* PUSH Fabien */
8417           case WhiteHSideCastleFR:
8418           case WhiteASideCastleFR:
8419           case BlackHSideCastleFR:
8420           case BlackASideCastleFR:
8421           /* POP Fabien */
8422             fromX = currentMoveString[0] - AAA;
8423             fromY = currentMoveString[1] - ONE;
8424             toX = currentMoveString[2] - AAA;
8425             toY = currentMoveString[3] - ONE;
8426             promoChar = currentMoveString[4];
8427             break;
8428           case WhiteDrop:
8429           case BlackDrop:
8430             fromX = moveType == WhiteDrop ?
8431               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8432             (int) CharToPiece(ToLower(currentMoveString[0]));
8433             fromY = DROP_RANK;
8434             toX = currentMoveString[2] - AAA;
8435             toY = currentMoveString[3] - ONE;
8436             promoChar = NULLCHAR;
8437             break;
8438           case AmbiguousMove:
8439             /* bug? */
8440             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8441   if (appData.debugMode) {
8442     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8443     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8444     setbuf(debugFP, NULL);
8445   }
8446             DisplayError(buf, 0);
8447             return;
8448           case ImpossibleMove:
8449             /* bug? */
8450             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8451   if (appData.debugMode) {
8452     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8453     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8454     setbuf(debugFP, NULL);
8455   }
8456             DisplayError(buf, 0);
8457             return;
8458           case EndOfFile:
8459             if (boardIndex < backwardMostMove) {
8460                 /* Oops, gap.  How did that happen? */
8461                 DisplayError(_("Gap in move list"), 0);
8462                 return;
8463             }
8464             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8465             if (boardIndex > forwardMostMove) {
8466                 forwardMostMove = boardIndex;
8467             }
8468             return;
8469           case ElapsedTime:
8470             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8471                 strcat(parseList[boardIndex-1], " ");
8472                 strcat(parseList[boardIndex-1], yy_text);
8473             }
8474             continue;
8475           case Comment:
8476           case PGNTag:
8477           case NAG:
8478           default:
8479             /* ignore */
8480             continue;
8481           case WhiteWins:
8482           case BlackWins:
8483           case GameIsDrawn:
8484           case GameUnfinished:
8485             if (gameMode == IcsExamining) {
8486                 if (boardIndex < backwardMostMove) {
8487                     /* Oops, gap.  How did that happen? */
8488                     return;
8489                 }
8490                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8491                 return;
8492             }
8493             gameInfo.result = moveType;
8494             p = strchr(yy_text, '{');
8495             if (p == NULL) p = strchr(yy_text, '(');
8496             if (p == NULL) {
8497                 p = yy_text;
8498                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8499             } else {
8500                 q = strchr(p, *p == '{' ? '}' : ')');
8501                 if (q != NULL) *q = NULLCHAR;
8502                 p++;
8503             }
8504             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8505             gameInfo.resultDetails = StrSave(p);
8506             continue;
8507         }
8508         if (boardIndex >= forwardMostMove &&
8509             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8510             backwardMostMove = blackPlaysFirst ? 1 : 0;
8511             return;
8512         }
8513         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8514                                  fromY, fromX, toY, toX, promoChar,
8515                                  parseList[boardIndex]);
8516         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8517         /* currentMoveString is set as a side-effect of yylex */
8518         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8519         strcat(moveList[boardIndex], "\n");
8520         boardIndex++;
8521         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8522         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8523           case MT_NONE:
8524           case MT_STALEMATE:
8525           default:
8526             break;
8527           case MT_CHECK:
8528             if(gameInfo.variant != VariantShogi)
8529                 strcat(parseList[boardIndex - 1], "+");
8530             break;
8531           case MT_CHECKMATE:
8532           case MT_STAINMATE:
8533             strcat(parseList[boardIndex - 1], "#");
8534             break;
8535         }
8536     }
8537 }
8538
8539
8540 /* Apply a move to the given board  */
8541 void
8542 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8543      int fromX, fromY, toX, toY;
8544      int promoChar;
8545      Board board;
8546 {
8547   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8548   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8549
8550     /* [HGM] compute & store e.p. status and castling rights for new position */
8551     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8552
8553       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8554       oldEP = (signed char)board[EP_STATUS];
8555       board[EP_STATUS] = EP_NONE;
8556
8557       if( board[toY][toX] != EmptySquare )
8558            board[EP_STATUS] = EP_CAPTURE;
8559
8560   if (fromY == DROP_RANK) {
8561         /* must be first */
8562         piece = board[toY][toX] = (ChessSquare) fromX;
8563   } else {
8564       int i;
8565
8566       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8567            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8568                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8569       } else
8570       if( board[fromY][fromX] == WhitePawn ) {
8571            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8572                board[EP_STATUS] = EP_PAWN_MOVE;
8573            if( toY-fromY==2) {
8574                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8575                         gameInfo.variant != VariantBerolina || toX < fromX)
8576                       board[EP_STATUS] = toX | berolina;
8577                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8578                         gameInfo.variant != VariantBerolina || toX > fromX)
8579                       board[EP_STATUS] = toX;
8580            }
8581       } else
8582       if( board[fromY][fromX] == BlackPawn ) {
8583            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8584                board[EP_STATUS] = EP_PAWN_MOVE;
8585            if( toY-fromY== -2) {
8586                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8587                         gameInfo.variant != VariantBerolina || toX < fromX)
8588                       board[EP_STATUS] = toX | berolina;
8589                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8590                         gameInfo.variant != VariantBerolina || toX > fromX)
8591                       board[EP_STATUS] = toX;
8592            }
8593        }
8594
8595        for(i=0; i<nrCastlingRights; i++) {
8596            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8597               board[CASTLING][i] == toX   && castlingRank[i] == toY
8598              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8599        }
8600
8601      if (fromX == toX && fromY == toY) return;
8602
8603      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8604      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8605      if(gameInfo.variant == VariantKnightmate)
8606          king += (int) WhiteUnicorn - (int) WhiteKing;
8607
8608     /* Code added by Tord: */
8609     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8610     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8611         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8612       board[fromY][fromX] = EmptySquare;
8613       board[toY][toX] = EmptySquare;
8614       if((toX > fromX) != (piece == WhiteRook)) {
8615         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8616       } else {
8617         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8618       }
8619     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8620                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8621       board[fromY][fromX] = EmptySquare;
8622       board[toY][toX] = EmptySquare;
8623       if((toX > fromX) != (piece == BlackRook)) {
8624         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8625       } else {
8626         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8627       }
8628     /* End of code added by Tord */
8629
8630     } else if (board[fromY][fromX] == king
8631         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8632         && toY == fromY && toX > fromX+1) {
8633         board[fromY][fromX] = EmptySquare;
8634         board[toY][toX] = king;
8635         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8636         board[fromY][BOARD_RGHT-1] = EmptySquare;
8637     } else if (board[fromY][fromX] == king
8638         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8639                && toY == fromY && toX < fromX-1) {
8640         board[fromY][fromX] = EmptySquare;
8641         board[toY][toX] = king;
8642         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8643         board[fromY][BOARD_LEFT] = EmptySquare;
8644     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8645                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8646                && toY >= BOARD_HEIGHT-promoRank
8647                ) {
8648         /* white pawn promotion */
8649         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8650         if (board[toY][toX] == EmptySquare) {
8651             board[toY][toX] = WhiteQueen;
8652         }
8653         if(gameInfo.variant==VariantBughouse ||
8654            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8655             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8656         board[fromY][fromX] = EmptySquare;
8657     } else if ((fromY == BOARD_HEIGHT-4)
8658                && (toX != fromX)
8659                && gameInfo.variant != VariantXiangqi
8660                && gameInfo.variant != VariantBerolina
8661                && (board[fromY][fromX] == WhitePawn)
8662                && (board[toY][toX] == EmptySquare)) {
8663         board[fromY][fromX] = EmptySquare;
8664         board[toY][toX] = WhitePawn;
8665         captured = board[toY - 1][toX];
8666         board[toY - 1][toX] = EmptySquare;
8667     } else if ((fromY == BOARD_HEIGHT-4)
8668                && (toX == fromX)
8669                && gameInfo.variant == VariantBerolina
8670                && (board[fromY][fromX] == WhitePawn)
8671                && (board[toY][toX] == EmptySquare)) {
8672         board[fromY][fromX] = EmptySquare;
8673         board[toY][toX] = WhitePawn;
8674         if(oldEP & EP_BEROLIN_A) {
8675                 captured = board[fromY][fromX-1];
8676                 board[fromY][fromX-1] = EmptySquare;
8677         }else{  captured = board[fromY][fromX+1];
8678                 board[fromY][fromX+1] = EmptySquare;
8679         }
8680     } else if (board[fromY][fromX] == king
8681         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8682                && toY == fromY && toX > fromX+1) {
8683         board[fromY][fromX] = EmptySquare;
8684         board[toY][toX] = king;
8685         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8686         board[fromY][BOARD_RGHT-1] = EmptySquare;
8687     } else if (board[fromY][fromX] == king
8688         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8689                && toY == fromY && toX < fromX-1) {
8690         board[fromY][fromX] = EmptySquare;
8691         board[toY][toX] = king;
8692         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8693         board[fromY][BOARD_LEFT] = EmptySquare;
8694     } else if (fromY == 7 && fromX == 3
8695                && board[fromY][fromX] == BlackKing
8696                && toY == 7 && toX == 5) {
8697         board[fromY][fromX] = EmptySquare;
8698         board[toY][toX] = BlackKing;
8699         board[fromY][7] = EmptySquare;
8700         board[toY][4] = BlackRook;
8701     } else if (fromY == 7 && fromX == 3
8702                && board[fromY][fromX] == BlackKing
8703                && toY == 7 && toX == 1) {
8704         board[fromY][fromX] = EmptySquare;
8705         board[toY][toX] = BlackKing;
8706         board[fromY][0] = EmptySquare;
8707         board[toY][2] = BlackRook;
8708     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8709                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8710                && toY < promoRank
8711                ) {
8712         /* black pawn promotion */
8713         board[toY][toX] = CharToPiece(ToLower(promoChar));
8714         if (board[toY][toX] == EmptySquare) {
8715             board[toY][toX] = BlackQueen;
8716         }
8717         if(gameInfo.variant==VariantBughouse ||
8718            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8719             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8720         board[fromY][fromX] = EmptySquare;
8721     } else if ((fromY == 3)
8722                && (toX != fromX)
8723                && gameInfo.variant != VariantXiangqi
8724                && gameInfo.variant != VariantBerolina
8725                && (board[fromY][fromX] == BlackPawn)
8726                && (board[toY][toX] == EmptySquare)) {
8727         board[fromY][fromX] = EmptySquare;
8728         board[toY][toX] = BlackPawn;
8729         captured = board[toY + 1][toX];
8730         board[toY + 1][toX] = EmptySquare;
8731     } else if ((fromY == 3)
8732                && (toX == fromX)
8733                && gameInfo.variant == VariantBerolina
8734                && (board[fromY][fromX] == BlackPawn)
8735                && (board[toY][toX] == EmptySquare)) {
8736         board[fromY][fromX] = EmptySquare;
8737         board[toY][toX] = BlackPawn;
8738         if(oldEP & EP_BEROLIN_A) {
8739                 captured = board[fromY][fromX-1];
8740                 board[fromY][fromX-1] = EmptySquare;
8741         }else{  captured = board[fromY][fromX+1];
8742                 board[fromY][fromX+1] = EmptySquare;
8743         }
8744     } else {
8745         board[toY][toX] = board[fromY][fromX];
8746         board[fromY][fromX] = EmptySquare;
8747     }
8748   }
8749
8750     if (gameInfo.holdingsWidth != 0) {
8751
8752       /* !!A lot more code needs to be written to support holdings  */
8753       /* [HGM] OK, so I have written it. Holdings are stored in the */
8754       /* penultimate board files, so they are automaticlly stored   */
8755       /* in the game history.                                       */
8756       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8757                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8758         /* Delete from holdings, by decreasing count */
8759         /* and erasing image if necessary            */
8760         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8761         if(p < (int) BlackPawn) { /* white drop */
8762              p -= (int)WhitePawn;
8763                  p = PieceToNumber((ChessSquare)p);
8764              if(p >= gameInfo.holdingsSize) p = 0;
8765              if(--board[p][BOARD_WIDTH-2] <= 0)
8766                   board[p][BOARD_WIDTH-1] = EmptySquare;
8767              if((int)board[p][BOARD_WIDTH-2] < 0)
8768                         board[p][BOARD_WIDTH-2] = 0;
8769         } else {                  /* black drop */
8770              p -= (int)BlackPawn;
8771                  p = PieceToNumber((ChessSquare)p);
8772              if(p >= gameInfo.holdingsSize) p = 0;
8773              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8774                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8775              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8776                         board[BOARD_HEIGHT-1-p][1] = 0;
8777         }
8778       }
8779       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8780           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8781         /* [HGM] holdings: Add to holdings, if holdings exist */
8782         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8783                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8784                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8785         }
8786         p = (int) captured;
8787         if (p >= (int) BlackPawn) {
8788           p -= (int)BlackPawn;
8789           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8790                   /* in Shogi restore piece to its original  first */
8791                   captured = (ChessSquare) (DEMOTED captured);
8792                   p = DEMOTED p;
8793           }
8794           p = PieceToNumber((ChessSquare)p);
8795           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8796           board[p][BOARD_WIDTH-2]++;
8797           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8798         } else {
8799           p -= (int)WhitePawn;
8800           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8801                   captured = (ChessSquare) (DEMOTED captured);
8802                   p = DEMOTED p;
8803           }
8804           p = PieceToNumber((ChessSquare)p);
8805           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8806           board[BOARD_HEIGHT-1-p][1]++;
8807           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8808         }
8809       }
8810     } else if (gameInfo.variant == VariantAtomic) {
8811       if (captured != EmptySquare) {
8812         int y, x;
8813         for (y = toY-1; y <= toY+1; y++) {
8814           for (x = toX-1; x <= toX+1; x++) {
8815             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8816                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8817               board[y][x] = EmptySquare;
8818             }
8819           }
8820         }
8821         board[toY][toX] = EmptySquare;
8822       }
8823     }
8824     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8825         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8826     } else
8827     if(promoChar == '+') {
8828         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8829         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8830     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8831         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8832     }
8833     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8834                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8835         // [HGM] superchess: take promotion piece out of holdings
8836         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8837         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8838             if(!--board[k][BOARD_WIDTH-2])
8839                 board[k][BOARD_WIDTH-1] = EmptySquare;
8840         } else {
8841             if(!--board[BOARD_HEIGHT-1-k][1])
8842                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8843         }
8844     }
8845
8846 }
8847
8848 /* Updates forwardMostMove */
8849 void
8850 MakeMove(fromX, fromY, toX, toY, promoChar)
8851      int fromX, fromY, toX, toY;
8852      int promoChar;
8853 {
8854 //    forwardMostMove++; // [HGM] bare: moved downstream
8855
8856     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8857         int timeLeft; static int lastLoadFlag=0; int king, piece;
8858         piece = boards[forwardMostMove][fromY][fromX];
8859         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8860         if(gameInfo.variant == VariantKnightmate)
8861             king += (int) WhiteUnicorn - (int) WhiteKing;
8862         if(forwardMostMove == 0) {
8863             if(blackPlaysFirst)
8864                 fprintf(serverMoves, "%s;", second.tidy);
8865             fprintf(serverMoves, "%s;", first.tidy);
8866             if(!blackPlaysFirst)
8867                 fprintf(serverMoves, "%s;", second.tidy);
8868         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8869         lastLoadFlag = loadFlag;
8870         // print base move
8871         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8872         // print castling suffix
8873         if( toY == fromY && piece == king ) {
8874             if(toX-fromX > 1)
8875                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8876             if(fromX-toX >1)
8877                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8878         }
8879         // e.p. suffix
8880         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8881              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8882              boards[forwardMostMove][toY][toX] == EmptySquare
8883              && fromX != toX && fromY != toY)
8884                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8885         // promotion suffix
8886         if(promoChar != NULLCHAR)
8887                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8888         if(!loadFlag) {
8889             fprintf(serverMoves, "/%d/%d",
8890                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8891             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8892             else                      timeLeft = blackTimeRemaining/1000;
8893             fprintf(serverMoves, "/%d", timeLeft);
8894         }
8895         fflush(serverMoves);
8896     }
8897
8898     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8899       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8900                         0, 1);
8901       return;
8902     }
8903     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8904     if (commentList[forwardMostMove+1] != NULL) {
8905         free(commentList[forwardMostMove+1]);
8906         commentList[forwardMostMove+1] = NULL;
8907     }
8908     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8909     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8910     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8911     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8912     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8913     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8914     gameInfo.result = GameUnfinished;
8915     if (gameInfo.resultDetails != NULL) {
8916         free(gameInfo.resultDetails);
8917         gameInfo.resultDetails = NULL;
8918     }
8919     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8920                               moveList[forwardMostMove - 1]);
8921     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8922                              PosFlags(forwardMostMove - 1),
8923                              fromY, fromX, toY, toX, promoChar,
8924                              parseList[forwardMostMove - 1]);
8925     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8926       case MT_NONE:
8927       case MT_STALEMATE:
8928       default:
8929         break;
8930       case MT_CHECK:
8931         if(gameInfo.variant != VariantShogi)
8932             strcat(parseList[forwardMostMove - 1], "+");
8933         break;
8934       case MT_CHECKMATE:
8935       case MT_STAINMATE:
8936         strcat(parseList[forwardMostMove - 1], "#");
8937         break;
8938     }
8939     if (appData.debugMode) {
8940         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8941     }
8942
8943 }
8944
8945 /* Updates currentMove if not pausing */
8946 void
8947 ShowMove(fromX, fromY, toX, toY)
8948 {
8949     int instant = (gameMode == PlayFromGameFile) ?
8950         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8951     if(appData.noGUI) return;
8952     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8953         if (!instant) {
8954             if (forwardMostMove == currentMove + 1) {
8955                 AnimateMove(boards[forwardMostMove - 1],
8956                             fromX, fromY, toX, toY);
8957             }
8958             if (appData.highlightLastMove) {
8959                 SetHighlights(fromX, fromY, toX, toY);
8960             }
8961         }
8962         currentMove = forwardMostMove;
8963     }
8964
8965     if (instant) return;
8966
8967     DisplayMove(currentMove - 1);
8968     DrawPosition(FALSE, boards[currentMove]);
8969     DisplayBothClocks();
8970     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8971 }
8972
8973 void SendEgtPath(ChessProgramState *cps)
8974 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8975         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8976
8977         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8978
8979         while(*p) {
8980             char c, *q = name+1, *r, *s;
8981
8982             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8983             while(*p && *p != ',') *q++ = *p++;
8984             *q++ = ':'; *q = 0;
8985             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8986                 strcmp(name, ",nalimov:") == 0 ) {
8987                 // take nalimov path from the menu-changeable option first, if it is defined
8988               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8989                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8990             } else
8991             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8992                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8993                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8994                 s = r = StrStr(s, ":") + 1; // beginning of path info
8995                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8996                 c = *r; *r = 0;             // temporarily null-terminate path info
8997                     *--q = 0;               // strip of trailig ':' from name
8998                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8999                 *r = c;
9000                 SendToProgram(buf,cps);     // send egtbpath command for this format
9001             }
9002             if(*p == ',') p++; // read away comma to position for next format name
9003         }
9004 }
9005
9006 void
9007 InitChessProgram(cps, setup)
9008      ChessProgramState *cps;
9009      int setup; /* [HGM] needed to setup FRC opening position */
9010 {
9011     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9012     if (appData.noChessProgram) return;
9013     hintRequested = FALSE;
9014     bookRequested = FALSE;
9015
9016     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9017     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9018     if(cps->memSize) { /* [HGM] memory */
9019       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9020         SendToProgram(buf, cps);
9021     }
9022     SendEgtPath(cps); /* [HGM] EGT */
9023     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9024       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9025         SendToProgram(buf, cps);
9026     }
9027
9028     SendToProgram(cps->initString, cps);
9029     if (gameInfo.variant != VariantNormal &&
9030         gameInfo.variant != VariantLoadable
9031         /* [HGM] also send variant if board size non-standard */
9032         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9033                                             ) {
9034       char *v = VariantName(gameInfo.variant);
9035       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9036         /* [HGM] in protocol 1 we have to assume all variants valid */
9037         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9038         DisplayFatalError(buf, 0, 1);
9039         return;
9040       }
9041
9042       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9043       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9044       if( gameInfo.variant == VariantXiangqi )
9045            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9046       if( gameInfo.variant == VariantShogi )
9047            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9048       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9049            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9050       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9051           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9052            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9053       if( gameInfo.variant == VariantCourier )
9054            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9055       if( gameInfo.variant == VariantSuper )
9056            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9057       if( gameInfo.variant == VariantGreat )
9058            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9059       if( gameInfo.variant == VariantSChess )
9060            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9061
9062       if(overruled) {
9063         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9064                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9065            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9066            if(StrStr(cps->variants, b) == NULL) {
9067                // specific sized variant not known, check if general sizing allowed
9068                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9069                    if(StrStr(cps->variants, "boardsize") == NULL) {
9070                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9071                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9072                        DisplayFatalError(buf, 0, 1);
9073                        return;
9074                    }
9075                    /* [HGM] here we really should compare with the maximum supported board size */
9076                }
9077            }
9078       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9079       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9080       SendToProgram(buf, cps);
9081     }
9082     currentlyInitializedVariant = gameInfo.variant;
9083
9084     /* [HGM] send opening position in FRC to first engine */
9085     if(setup) {
9086           SendToProgram("force\n", cps);
9087           SendBoard(cps, 0);
9088           /* engine is now in force mode! Set flag to wake it up after first move. */
9089           setboardSpoiledMachineBlack = 1;
9090     }
9091
9092     if (cps->sendICS) {
9093       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9094       SendToProgram(buf, cps);
9095     }
9096     cps->maybeThinking = FALSE;
9097     cps->offeredDraw = 0;
9098     if (!appData.icsActive) {
9099         SendTimeControl(cps, movesPerSession, timeControl,
9100                         timeIncrement, appData.searchDepth,
9101                         searchTime);
9102     }
9103     if (appData.showThinking
9104         // [HGM] thinking: four options require thinking output to be sent
9105         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9106                                 ) {
9107         SendToProgram("post\n", cps);
9108     }
9109     SendToProgram("hard\n", cps);
9110     if (!appData.ponderNextMove) {
9111         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9112            it without being sure what state we are in first.  "hard"
9113            is not a toggle, so that one is OK.
9114          */
9115         SendToProgram("easy\n", cps);
9116     }
9117     if (cps->usePing) {
9118       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9119       SendToProgram(buf, cps);
9120     }
9121     cps->initDone = TRUE;
9122 }
9123
9124
9125 void
9126 StartChessProgram(cps)
9127      ChessProgramState *cps;
9128 {
9129     char buf[MSG_SIZ];
9130     int err;
9131
9132     if (appData.noChessProgram) return;
9133     cps->initDone = FALSE;
9134
9135     if (strcmp(cps->host, "localhost") == 0) {
9136         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9137     } else if (*appData.remoteShell == NULLCHAR) {
9138         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9139     } else {
9140         if (*appData.remoteUser == NULLCHAR) {
9141           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9142                     cps->program);
9143         } else {
9144           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9145                     cps->host, appData.remoteUser, cps->program);
9146         }
9147         err = StartChildProcess(buf, "", &cps->pr);
9148     }
9149
9150     if (err != 0) {
9151       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9152         DisplayFatalError(buf, err, 1);
9153         cps->pr = NoProc;
9154         cps->isr = NULL;
9155         return;
9156     }
9157
9158     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9159     if (cps->protocolVersion > 1) {
9160       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9161       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9162       cps->comboCnt = 0;  //                and values of combo boxes
9163       SendToProgram(buf, cps);
9164     } else {
9165       SendToProgram("xboard\n", cps);
9166     }
9167 }
9168
9169
9170 void
9171 TwoMachinesEventIfReady P((void))
9172 {
9173   if (first.lastPing != first.lastPong) {
9174     DisplayMessage("", _("Waiting for first chess program"));
9175     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9176     return;
9177   }
9178   if (second.lastPing != second.lastPong) {
9179     DisplayMessage("", _("Waiting for second chess program"));
9180     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9181     return;
9182   }
9183   ThawUI();
9184   TwoMachinesEvent();
9185 }
9186
9187 void
9188 NextMatchGame P((void))
9189 {
9190     int index; /* [HGM] autoinc: step load index during match */
9191     Reset(FALSE, TRUE);
9192     if (*appData.loadGameFile != NULLCHAR) {
9193         index = appData.loadGameIndex;
9194         if(index < 0) { // [HGM] autoinc
9195             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9196             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9197         }
9198         LoadGameFromFile(appData.loadGameFile,
9199                          index,
9200                          appData.loadGameFile, FALSE);
9201     } else if (*appData.loadPositionFile != NULLCHAR) {
9202         index = appData.loadPositionIndex;
9203         if(index < 0) { // [HGM] autoinc
9204             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9205             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9206         }
9207         LoadPositionFromFile(appData.loadPositionFile,
9208                              index,
9209                              appData.loadPositionFile);
9210     }
9211     TwoMachinesEventIfReady();
9212 }
9213
9214 void UserAdjudicationEvent( int result )
9215 {
9216     ChessMove gameResult = GameIsDrawn;
9217
9218     if( result > 0 ) {
9219         gameResult = WhiteWins;
9220     }
9221     else if( result < 0 ) {
9222         gameResult = BlackWins;
9223     }
9224
9225     if( gameMode == TwoMachinesPlay ) {
9226         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9227     }
9228 }
9229
9230
9231 // [HGM] save: calculate checksum of game to make games easily identifiable
9232 int StringCheckSum(char *s)
9233 {
9234         int i = 0;
9235         if(s==NULL) return 0;
9236         while(*s) i = i*259 + *s++;
9237         return i;
9238 }
9239
9240 int GameCheckSum()
9241 {
9242         int i, sum=0;
9243         for(i=backwardMostMove; i<forwardMostMove; i++) {
9244                 sum += pvInfoList[i].depth;
9245                 sum += StringCheckSum(parseList[i]);
9246                 sum += StringCheckSum(commentList[i]);
9247                 sum *= 261;
9248         }
9249         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9250         return sum + StringCheckSum(commentList[i]);
9251 } // end of save patch
9252
9253 void
9254 GameEnds(result, resultDetails, whosays)
9255      ChessMove result;
9256      char *resultDetails;
9257      int whosays;
9258 {
9259     GameMode nextGameMode;
9260     int isIcsGame;
9261     char buf[MSG_SIZ], popupRequested = 0;
9262
9263     if(endingGame) return; /* [HGM] crash: forbid recursion */
9264     endingGame = 1;
9265     if(twoBoards) { // [HGM] dual: switch back to one board
9266         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9267         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9268     }
9269     if (appData.debugMode) {
9270       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9271               result, resultDetails ? resultDetails : "(null)", whosays);
9272     }
9273
9274     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9275
9276     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9277         /* If we are playing on ICS, the server decides when the
9278            game is over, but the engine can offer to draw, claim
9279            a draw, or resign.
9280          */
9281 #if ZIPPY
9282         if (appData.zippyPlay && first.initDone) {
9283             if (result == GameIsDrawn) {
9284                 /* In case draw still needs to be claimed */
9285                 SendToICS(ics_prefix);
9286                 SendToICS("draw\n");
9287             } else if (StrCaseStr(resultDetails, "resign")) {
9288                 SendToICS(ics_prefix);
9289                 SendToICS("resign\n");
9290             }
9291         }
9292 #endif
9293         endingGame = 0; /* [HGM] crash */
9294         return;
9295     }
9296
9297     /* If we're loading the game from a file, stop */
9298     if (whosays == GE_FILE) {
9299       (void) StopLoadGameTimer();
9300       gameFileFP = NULL;
9301     }
9302
9303     /* Cancel draw offers */
9304     first.offeredDraw = second.offeredDraw = 0;
9305
9306     /* If this is an ICS game, only ICS can really say it's done;
9307        if not, anyone can. */
9308     isIcsGame = (gameMode == IcsPlayingWhite ||
9309                  gameMode == IcsPlayingBlack ||
9310                  gameMode == IcsObserving    ||
9311                  gameMode == IcsExamining);
9312
9313     if (!isIcsGame || whosays == GE_ICS) {
9314         /* OK -- not an ICS game, or ICS said it was done */
9315         StopClocks();
9316         if (!isIcsGame && !appData.noChessProgram)
9317           SetUserThinkingEnables();
9318
9319         /* [HGM] if a machine claims the game end we verify this claim */
9320         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9321             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9322                 char claimer;
9323                 ChessMove trueResult = (ChessMove) -1;
9324
9325                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9326                                             first.twoMachinesColor[0] :
9327                                             second.twoMachinesColor[0] ;
9328
9329                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9330                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9331                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9332                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9333                 } else
9334                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9335                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9336                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9337                 } else
9338                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9339                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9340                 }
9341
9342                 // now verify win claims, but not in drop games, as we don't understand those yet
9343                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9344                                                  || gameInfo.variant == VariantGreat) &&
9345                     (result == WhiteWins && claimer == 'w' ||
9346                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9347                       if (appData.debugMode) {
9348                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9349                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9350                       }
9351                       if(result != trueResult) {
9352                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9353                               result = claimer == 'w' ? BlackWins : WhiteWins;
9354                               resultDetails = buf;
9355                       }
9356                 } else
9357                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9358                     && (forwardMostMove <= backwardMostMove ||
9359                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9360                         (claimer=='b')==(forwardMostMove&1))
9361                                                                                   ) {
9362                       /* [HGM] verify: draws that were not flagged are false claims */
9363                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9364                       result = claimer == 'w' ? BlackWins : WhiteWins;
9365                       resultDetails = buf;
9366                 }
9367                 /* (Claiming a loss is accepted no questions asked!) */
9368             }
9369             /* [HGM] bare: don't allow bare King to win */
9370             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9371                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9372                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9373                && result != GameIsDrawn)
9374             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9375                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9376                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9377                         if(p >= 0 && p <= (int)WhiteKing) k++;
9378                 }
9379                 if (appData.debugMode) {
9380                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9381                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9382                 }
9383                 if(k <= 1) {
9384                         result = GameIsDrawn;
9385                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9386                         resultDetails = buf;
9387                 }
9388             }
9389         }
9390
9391
9392         if(serverMoves != NULL && !loadFlag) { char c = '=';
9393             if(result==WhiteWins) c = '+';
9394             if(result==BlackWins) c = '-';
9395             if(resultDetails != NULL)
9396                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9397         }
9398         if (resultDetails != NULL) {
9399             gameInfo.result = result;
9400             gameInfo.resultDetails = StrSave(resultDetails);
9401
9402             /* display last move only if game was not loaded from file */
9403             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9404                 DisplayMove(currentMove - 1);
9405
9406             if (forwardMostMove != 0) {
9407                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9408                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9409                                                                 ) {
9410                     if (*appData.saveGameFile != NULLCHAR) {
9411                         SaveGameToFile(appData.saveGameFile, TRUE);
9412                     } else if (appData.autoSaveGames) {
9413                         AutoSaveGame();
9414                     }
9415                     if (*appData.savePositionFile != NULLCHAR) {
9416                         SavePositionToFile(appData.savePositionFile);
9417                     }
9418                 }
9419             }
9420
9421             /* Tell program how game ended in case it is learning */
9422             /* [HGM] Moved this to after saving the PGN, just in case */
9423             /* engine died and we got here through time loss. In that */
9424             /* case we will get a fatal error writing the pipe, which */
9425             /* would otherwise lose us the PGN.                       */
9426             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9427             /* output during GameEnds should never be fatal anymore   */
9428             if (gameMode == MachinePlaysWhite ||
9429                 gameMode == MachinePlaysBlack ||
9430                 gameMode == TwoMachinesPlay ||
9431                 gameMode == IcsPlayingWhite ||
9432                 gameMode == IcsPlayingBlack ||
9433                 gameMode == BeginningOfGame) {
9434                 char buf[MSG_SIZ];
9435                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9436                         resultDetails);
9437                 if (first.pr != NoProc) {
9438                     SendToProgram(buf, &first);
9439                 }
9440                 if (second.pr != NoProc &&
9441                     gameMode == TwoMachinesPlay) {
9442                     SendToProgram(buf, &second);
9443                 }
9444             }
9445         }
9446
9447         if (appData.icsActive) {
9448             if (appData.quietPlay &&
9449                 (gameMode == IcsPlayingWhite ||
9450                  gameMode == IcsPlayingBlack)) {
9451                 SendToICS(ics_prefix);
9452                 SendToICS("set shout 1\n");
9453             }
9454             nextGameMode = IcsIdle;
9455             ics_user_moved = FALSE;
9456             /* clean up premove.  It's ugly when the game has ended and the
9457              * premove highlights are still on the board.
9458              */
9459             if (gotPremove) {
9460               gotPremove = FALSE;
9461               ClearPremoveHighlights();
9462               DrawPosition(FALSE, boards[currentMove]);
9463             }
9464             if (whosays == GE_ICS) {
9465                 switch (result) {
9466                 case WhiteWins:
9467                     if (gameMode == IcsPlayingWhite)
9468                         PlayIcsWinSound();
9469                     else if(gameMode == IcsPlayingBlack)
9470                         PlayIcsLossSound();
9471                     break;
9472                 case BlackWins:
9473                     if (gameMode == IcsPlayingBlack)
9474                         PlayIcsWinSound();
9475                     else if(gameMode == IcsPlayingWhite)
9476                         PlayIcsLossSound();
9477                     break;
9478                 case GameIsDrawn:
9479                     PlayIcsDrawSound();
9480                     break;
9481                 default:
9482                     PlayIcsUnfinishedSound();
9483                 }
9484             }
9485         } else if (gameMode == EditGame ||
9486                    gameMode == PlayFromGameFile ||
9487                    gameMode == AnalyzeMode ||
9488                    gameMode == AnalyzeFile) {
9489             nextGameMode = gameMode;
9490         } else {
9491             nextGameMode = EndOfGame;
9492         }
9493         pausing = FALSE;
9494         ModeHighlight();
9495     } else {
9496         nextGameMode = gameMode;
9497     }
9498
9499     if (appData.noChessProgram) {
9500         gameMode = nextGameMode;
9501         ModeHighlight();
9502         endingGame = 0; /* [HGM] crash */
9503         return;
9504     }
9505
9506     if (first.reuse) {
9507         /* Put first chess program into idle state */
9508         if (first.pr != NoProc &&
9509             (gameMode == MachinePlaysWhite ||
9510              gameMode == MachinePlaysBlack ||
9511              gameMode == TwoMachinesPlay ||
9512              gameMode == IcsPlayingWhite ||
9513              gameMode == IcsPlayingBlack ||
9514              gameMode == BeginningOfGame)) {
9515             SendToProgram("force\n", &first);
9516             if (first.usePing) {
9517               char buf[MSG_SIZ];
9518               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9519               SendToProgram(buf, &first);
9520             }
9521         }
9522     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9523         /* Kill off first chess program */
9524         if (first.isr != NULL)
9525           RemoveInputSource(first.isr);
9526         first.isr = NULL;
9527
9528         if (first.pr != NoProc) {
9529             ExitAnalyzeMode();
9530             DoSleep( appData.delayBeforeQuit );
9531             SendToProgram("quit\n", &first);
9532             DoSleep( appData.delayAfterQuit );
9533             DestroyChildProcess(first.pr, first.useSigterm);
9534         }
9535         first.pr = NoProc;
9536     }
9537     if (second.reuse) {
9538         /* Put second chess program into idle state */
9539         if (second.pr != NoProc &&
9540             gameMode == TwoMachinesPlay) {
9541             SendToProgram("force\n", &second);
9542             if (second.usePing) {
9543               char buf[MSG_SIZ];
9544               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9545               SendToProgram(buf, &second);
9546             }
9547         }
9548     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9549         /* Kill off second chess program */
9550         if (second.isr != NULL)
9551           RemoveInputSource(second.isr);
9552         second.isr = NULL;
9553
9554         if (second.pr != NoProc) {
9555             DoSleep( appData.delayBeforeQuit );
9556             SendToProgram("quit\n", &second);
9557             DoSleep( appData.delayAfterQuit );
9558             DestroyChildProcess(second.pr, second.useSigterm);
9559         }
9560         second.pr = NoProc;
9561     }
9562
9563     if (matchMode && gameMode == TwoMachinesPlay) {
9564         switch (result) {
9565         case WhiteWins:
9566           if (first.twoMachinesColor[0] == 'w') {
9567             first.matchWins++;
9568           } else {
9569             second.matchWins++;
9570           }
9571           break;
9572         case BlackWins:
9573           if (first.twoMachinesColor[0] == 'b') {
9574             first.matchWins++;
9575           } else {
9576             second.matchWins++;
9577           }
9578           break;
9579         default:
9580           break;
9581         }
9582         if (matchGame < appData.matchGames) {
9583             char *tmp;
9584             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9585                 tmp = first.twoMachinesColor;
9586                 first.twoMachinesColor = second.twoMachinesColor;
9587                 second.twoMachinesColor = tmp;
9588             }
9589             gameMode = nextGameMode;
9590             matchGame++;
9591             if(appData.matchPause>10000 || appData.matchPause<10)
9592                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9593             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9594             endingGame = 0; /* [HGM] crash */
9595             return;
9596         } else {
9597             gameMode = nextGameMode;
9598             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9599                      first.tidy, second.tidy,
9600                      first.matchWins, second.matchWins,
9601                      appData.matchGames - (first.matchWins + second.matchWins));
9602             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9603             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9604                 first.twoMachinesColor = "black\n";
9605                 second.twoMachinesColor = "white\n";
9606             } else {
9607                 first.twoMachinesColor = "white\n";
9608                 second.twoMachinesColor = "black\n";
9609             }
9610         }
9611     }
9612     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9613         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9614       ExitAnalyzeMode();
9615     gameMode = nextGameMode;
9616     ModeHighlight();
9617     endingGame = 0;  /* [HGM] crash */
9618     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9619       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9620         matchMode = FALSE; appData.matchGames = matchGame = 0;
9621         DisplayNote(buf);
9622       }
9623     }
9624 }
9625
9626 /* Assumes program was just initialized (initString sent).
9627    Leaves program in force mode. */
9628 void
9629 FeedMovesToProgram(cps, upto)
9630      ChessProgramState *cps;
9631      int upto;
9632 {
9633     int i;
9634
9635     if (appData.debugMode)
9636       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9637               startedFromSetupPosition ? "position and " : "",
9638               backwardMostMove, upto, cps->which);
9639     if(currentlyInitializedVariant != gameInfo.variant) {
9640       char buf[MSG_SIZ];
9641         // [HGM] variantswitch: make engine aware of new variant
9642         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9643                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9644         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9645         SendToProgram(buf, cps);
9646         currentlyInitializedVariant = gameInfo.variant;
9647     }
9648     SendToProgram("force\n", cps);
9649     if (startedFromSetupPosition) {
9650         SendBoard(cps, backwardMostMove);
9651     if (appData.debugMode) {
9652         fprintf(debugFP, "feedMoves\n");
9653     }
9654     }
9655     for (i = backwardMostMove; i < upto; i++) {
9656         SendMoveToProgram(i, cps);
9657     }
9658 }
9659
9660
9661 void
9662 ResurrectChessProgram()
9663 {
9664      /* The chess program may have exited.
9665         If so, restart it and feed it all the moves made so far. */
9666
9667     if (appData.noChessProgram || first.pr != NoProc) return;
9668
9669     StartChessProgram(&first);
9670     InitChessProgram(&first, FALSE);
9671     FeedMovesToProgram(&first, currentMove);
9672
9673     if (!first.sendTime) {
9674         /* can't tell gnuchess what its clock should read,
9675            so we bow to its notion. */
9676         ResetClocks();
9677         timeRemaining[0][currentMove] = whiteTimeRemaining;
9678         timeRemaining[1][currentMove] = blackTimeRemaining;
9679     }
9680
9681     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9682                 appData.icsEngineAnalyze) && first.analysisSupport) {
9683       SendToProgram("analyze\n", &first);
9684       first.analyzing = TRUE;
9685     }
9686 }
9687
9688 /*
9689  * Button procedures
9690  */
9691 void
9692 Reset(redraw, init)
9693      int redraw, init;
9694 {
9695     int i;
9696
9697     if (appData.debugMode) {
9698         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9699                 redraw, init, gameMode);
9700     }
9701     CleanupTail(); // [HGM] vari: delete any stored variations
9702     pausing = pauseExamInvalid = FALSE;
9703     startedFromSetupPosition = blackPlaysFirst = FALSE;
9704     firstMove = TRUE;
9705     whiteFlag = blackFlag = FALSE;
9706     userOfferedDraw = FALSE;
9707     hintRequested = bookRequested = FALSE;
9708     first.maybeThinking = FALSE;
9709     second.maybeThinking = FALSE;
9710     first.bookSuspend = FALSE; // [HGM] book
9711     second.bookSuspend = FALSE;
9712     thinkOutput[0] = NULLCHAR;
9713     lastHint[0] = NULLCHAR;
9714     ClearGameInfo(&gameInfo);
9715     gameInfo.variant = StringToVariant(appData.variant);
9716     ics_user_moved = ics_clock_paused = FALSE;
9717     ics_getting_history = H_FALSE;
9718     ics_gamenum = -1;
9719     white_holding[0] = black_holding[0] = NULLCHAR;
9720     ClearProgramStats();
9721     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9722
9723     ResetFrontEnd();
9724     ClearHighlights();
9725     flipView = appData.flipView;
9726     ClearPremoveHighlights();
9727     gotPremove = FALSE;
9728     alarmSounded = FALSE;
9729
9730     GameEnds(EndOfFile, NULL, GE_PLAYER);
9731     if(appData.serverMovesName != NULL) {
9732         /* [HGM] prepare to make moves file for broadcasting */
9733         clock_t t = clock();
9734         if(serverMoves != NULL) fclose(serverMoves);
9735         serverMoves = fopen(appData.serverMovesName, "r");
9736         if(serverMoves != NULL) {
9737             fclose(serverMoves);
9738             /* delay 15 sec before overwriting, so all clients can see end */
9739             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9740         }
9741         serverMoves = fopen(appData.serverMovesName, "w");
9742     }
9743
9744     ExitAnalyzeMode();
9745     gameMode = BeginningOfGame;
9746     ModeHighlight();
9747     if(appData.icsActive) gameInfo.variant = VariantNormal;
9748     currentMove = forwardMostMove = backwardMostMove = 0;
9749     InitPosition(redraw);
9750     for (i = 0; i < MAX_MOVES; i++) {
9751         if (commentList[i] != NULL) {
9752             free(commentList[i]);
9753             commentList[i] = NULL;
9754         }
9755     }
9756     ResetClocks();
9757     timeRemaining[0][0] = whiteTimeRemaining;
9758     timeRemaining[1][0] = blackTimeRemaining;
9759     if (first.pr == NULL) {
9760         StartChessProgram(&first);
9761     }
9762     if (init) {
9763             InitChessProgram(&first, startedFromSetupPosition);
9764     }
9765     DisplayTitle("");
9766     DisplayMessage("", "");
9767     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9768     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9769 }
9770
9771 void
9772 AutoPlayGameLoop()
9773 {
9774     for (;;) {
9775         if (!AutoPlayOneMove())
9776           return;
9777         if (matchMode || appData.timeDelay == 0)
9778           continue;
9779         if (appData.timeDelay < 0)
9780           return;
9781         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9782         break;
9783     }
9784 }
9785
9786
9787 int
9788 AutoPlayOneMove()
9789 {
9790     int fromX, fromY, toX, toY;
9791
9792     if (appData.debugMode) {
9793       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9794     }
9795
9796     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9797       return FALSE;
9798
9799     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9800       pvInfoList[currentMove].depth = programStats.depth;
9801       pvInfoList[currentMove].score = programStats.score;
9802       pvInfoList[currentMove].time  = 0;
9803       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9804     }
9805
9806     if (currentMove >= forwardMostMove) {
9807       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9808       gameMode = EditGame;
9809       ModeHighlight();
9810
9811       /* [AS] Clear current move marker at the end of a game */
9812       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9813
9814       return FALSE;
9815     }
9816
9817     toX = moveList[currentMove][2] - AAA;
9818     toY = moveList[currentMove][3] - ONE;
9819
9820     if (moveList[currentMove][1] == '@') {
9821         if (appData.highlightLastMove) {
9822             SetHighlights(-1, -1, toX, toY);
9823         }
9824     } else {
9825         fromX = moveList[currentMove][0] - AAA;
9826         fromY = moveList[currentMove][1] - ONE;
9827
9828         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9829
9830         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9831
9832         if (appData.highlightLastMove) {
9833             SetHighlights(fromX, fromY, toX, toY);
9834         }
9835     }
9836     DisplayMove(currentMove);
9837     SendMoveToProgram(currentMove++, &first);
9838     DisplayBothClocks();
9839     DrawPosition(FALSE, boards[currentMove]);
9840     // [HGM] PV info: always display, routine tests if empty
9841     DisplayComment(currentMove - 1, commentList[currentMove]);
9842     return TRUE;
9843 }
9844
9845
9846 int
9847 LoadGameOneMove(readAhead)
9848      ChessMove readAhead;
9849 {
9850     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9851     char promoChar = NULLCHAR;
9852     ChessMove moveType;
9853     char move[MSG_SIZ];
9854     char *p, *q;
9855
9856     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9857         gameMode != AnalyzeMode && gameMode != Training) {
9858         gameFileFP = NULL;
9859         return FALSE;
9860     }
9861
9862     yyboardindex = forwardMostMove;
9863     if (readAhead != EndOfFile) {
9864       moveType = readAhead;
9865     } else {
9866       if (gameFileFP == NULL)
9867           return FALSE;
9868       moveType = (ChessMove) Myylex();
9869     }
9870
9871     done = FALSE;
9872     switch (moveType) {
9873       case Comment:
9874         if (appData.debugMode)
9875           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9876         p = yy_text;
9877
9878         /* append the comment but don't display it */
9879         AppendComment(currentMove, p, FALSE);
9880         return TRUE;
9881
9882       case WhiteCapturesEnPassant:
9883       case BlackCapturesEnPassant:
9884       case WhitePromotion:
9885       case BlackPromotion:
9886       case WhiteNonPromotion:
9887       case BlackNonPromotion:
9888       case NormalMove:
9889       case WhiteKingSideCastle:
9890       case WhiteQueenSideCastle:
9891       case BlackKingSideCastle:
9892       case BlackQueenSideCastle:
9893       case WhiteKingSideCastleWild:
9894       case WhiteQueenSideCastleWild:
9895       case BlackKingSideCastleWild:
9896       case BlackQueenSideCastleWild:
9897       /* PUSH Fabien */
9898       case WhiteHSideCastleFR:
9899       case WhiteASideCastleFR:
9900       case BlackHSideCastleFR:
9901       case BlackASideCastleFR:
9902       /* POP Fabien */
9903         if (appData.debugMode)
9904           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9905         fromX = currentMoveString[0] - AAA;
9906         fromY = currentMoveString[1] - ONE;
9907         toX = currentMoveString[2] - AAA;
9908         toY = currentMoveString[3] - ONE;
9909         promoChar = currentMoveString[4];
9910         break;
9911
9912       case WhiteDrop:
9913       case BlackDrop:
9914         if (appData.debugMode)
9915           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9916         fromX = moveType == WhiteDrop ?
9917           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9918         (int) CharToPiece(ToLower(currentMoveString[0]));
9919         fromY = DROP_RANK;
9920         toX = currentMoveString[2] - AAA;
9921         toY = currentMoveString[3] - ONE;
9922         break;
9923
9924       case WhiteWins:
9925       case BlackWins:
9926       case GameIsDrawn:
9927       case GameUnfinished:
9928         if (appData.debugMode)
9929           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9930         p = strchr(yy_text, '{');
9931         if (p == NULL) p = strchr(yy_text, '(');
9932         if (p == NULL) {
9933             p = yy_text;
9934             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9935         } else {
9936             q = strchr(p, *p == '{' ? '}' : ')');
9937             if (q != NULL) *q = NULLCHAR;
9938             p++;
9939         }
9940         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9941         GameEnds(moveType, p, GE_FILE);
9942         done = TRUE;
9943         if (cmailMsgLoaded) {
9944             ClearHighlights();
9945             flipView = WhiteOnMove(currentMove);
9946             if (moveType == GameUnfinished) flipView = !flipView;
9947             if (appData.debugMode)
9948               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9949         }
9950         break;
9951
9952       case EndOfFile:
9953         if (appData.debugMode)
9954           fprintf(debugFP, "Parser hit end of file\n");
9955         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9956           case MT_NONE:
9957           case MT_CHECK:
9958             break;
9959           case MT_CHECKMATE:
9960           case MT_STAINMATE:
9961             if (WhiteOnMove(currentMove)) {
9962                 GameEnds(BlackWins, "Black mates", GE_FILE);
9963             } else {
9964                 GameEnds(WhiteWins, "White mates", GE_FILE);
9965             }
9966             break;
9967           case MT_STALEMATE:
9968             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9969             break;
9970         }
9971         done = TRUE;
9972         break;
9973
9974       case MoveNumberOne:
9975         if (lastLoadGameStart == GNUChessGame) {
9976             /* GNUChessGames have numbers, but they aren't move numbers */
9977             if (appData.debugMode)
9978               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9979                       yy_text, (int) moveType);
9980             return LoadGameOneMove(EndOfFile); /* tail recursion */
9981         }
9982         /* else fall thru */
9983
9984       case XBoardGame:
9985       case GNUChessGame:
9986       case PGNTag:
9987         /* Reached start of next game in file */
9988         if (appData.debugMode)
9989           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9990         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9991           case MT_NONE:
9992           case MT_CHECK:
9993             break;
9994           case MT_CHECKMATE:
9995           case MT_STAINMATE:
9996             if (WhiteOnMove(currentMove)) {
9997                 GameEnds(BlackWins, "Black mates", GE_FILE);
9998             } else {
9999                 GameEnds(WhiteWins, "White mates", GE_FILE);
10000             }
10001             break;
10002           case MT_STALEMATE:
10003             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10004             break;
10005         }
10006         done = TRUE;
10007         break;
10008
10009       case PositionDiagram:     /* should not happen; ignore */
10010       case ElapsedTime:         /* ignore */
10011       case NAG:                 /* ignore */
10012         if (appData.debugMode)
10013           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10014                   yy_text, (int) moveType);
10015         return LoadGameOneMove(EndOfFile); /* tail recursion */
10016
10017       case IllegalMove:
10018         if (appData.testLegality) {
10019             if (appData.debugMode)
10020               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10021             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10022                     (forwardMostMove / 2) + 1,
10023                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10024             DisplayError(move, 0);
10025             done = TRUE;
10026         } else {
10027             if (appData.debugMode)
10028               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10029                       yy_text, currentMoveString);
10030             fromX = currentMoveString[0] - AAA;
10031             fromY = currentMoveString[1] - ONE;
10032             toX = currentMoveString[2] - AAA;
10033             toY = currentMoveString[3] - ONE;
10034             promoChar = currentMoveString[4];
10035         }
10036         break;
10037
10038       case AmbiguousMove:
10039         if (appData.debugMode)
10040           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10041         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10042                 (forwardMostMove / 2) + 1,
10043                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10044         DisplayError(move, 0);
10045         done = TRUE;
10046         break;
10047
10048       default:
10049       case ImpossibleMove:
10050         if (appData.debugMode)
10051           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10052         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10053                 (forwardMostMove / 2) + 1,
10054                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10055         DisplayError(move, 0);
10056         done = TRUE;
10057         break;
10058     }
10059
10060     if (done) {
10061         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10062             DrawPosition(FALSE, boards[currentMove]);
10063             DisplayBothClocks();
10064             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10065               DisplayComment(currentMove - 1, commentList[currentMove]);
10066         }
10067         (void) StopLoadGameTimer();
10068         gameFileFP = NULL;
10069         cmailOldMove = forwardMostMove;
10070         return FALSE;
10071     } else {
10072         /* currentMoveString is set as a side-effect of yylex */
10073
10074         thinkOutput[0] = NULLCHAR;
10075         MakeMove(fromX, fromY, toX, toY, promoChar);
10076         currentMove = forwardMostMove;
10077         return TRUE;
10078     }
10079 }
10080
10081 /* Load the nth game from the given file */
10082 int
10083 LoadGameFromFile(filename, n, title, useList)
10084      char *filename;
10085      int n;
10086      char *title;
10087      /*Boolean*/ int useList;
10088 {
10089     FILE *f;
10090     char buf[MSG_SIZ];
10091
10092     if (strcmp(filename, "-") == 0) {
10093         f = stdin;
10094         title = "stdin";
10095     } else {
10096         f = fopen(filename, "rb");
10097         if (f == NULL) {
10098           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10099             DisplayError(buf, errno);
10100             return FALSE;
10101         }
10102     }
10103     if (fseek(f, 0, 0) == -1) {
10104         /* f is not seekable; probably a pipe */
10105         useList = FALSE;
10106     }
10107     if (useList && n == 0) {
10108         int error = GameListBuild(f);
10109         if (error) {
10110             DisplayError(_("Cannot build game list"), error);
10111         } else if (!ListEmpty(&gameList) &&
10112                    ((ListGame *) gameList.tailPred)->number > 1) {
10113             GameListPopUp(f, title);
10114             return TRUE;
10115         }
10116         GameListDestroy();
10117         n = 1;
10118     }
10119     if (n == 0) n = 1;
10120     return LoadGame(f, n, title, FALSE);
10121 }
10122
10123
10124 void
10125 MakeRegisteredMove()
10126 {
10127     int fromX, fromY, toX, toY;
10128     char promoChar;
10129     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10130         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10131           case CMAIL_MOVE:
10132           case CMAIL_DRAW:
10133             if (appData.debugMode)
10134               fprintf(debugFP, "Restoring %s for game %d\n",
10135                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10136
10137             thinkOutput[0] = NULLCHAR;
10138             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10139             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10140             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10141             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10142             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10143             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10144             MakeMove(fromX, fromY, toX, toY, promoChar);
10145             ShowMove(fromX, fromY, toX, toY);
10146
10147             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10148               case MT_NONE:
10149               case MT_CHECK:
10150                 break;
10151
10152               case MT_CHECKMATE:
10153               case MT_STAINMATE:
10154                 if (WhiteOnMove(currentMove)) {
10155                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10156                 } else {
10157                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10158                 }
10159                 break;
10160
10161               case MT_STALEMATE:
10162                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10163                 break;
10164             }
10165
10166             break;
10167
10168           case CMAIL_RESIGN:
10169             if (WhiteOnMove(currentMove)) {
10170                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10171             } else {
10172                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10173             }
10174             break;
10175
10176           case CMAIL_ACCEPT:
10177             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10178             break;
10179
10180           default:
10181             break;
10182         }
10183     }
10184
10185     return;
10186 }
10187
10188 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10189 int
10190 CmailLoadGame(f, gameNumber, title, useList)
10191      FILE *f;
10192      int gameNumber;
10193      char *title;
10194      int useList;
10195 {
10196     int retVal;
10197
10198     if (gameNumber > nCmailGames) {
10199         DisplayError(_("No more games in this message"), 0);
10200         return FALSE;
10201     }
10202     if (f == lastLoadGameFP) {
10203         int offset = gameNumber - lastLoadGameNumber;
10204         if (offset == 0) {
10205             cmailMsg[0] = NULLCHAR;
10206             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10207                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10208                 nCmailMovesRegistered--;
10209             }
10210             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10211             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10212                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10213             }
10214         } else {
10215             if (! RegisterMove()) return FALSE;
10216         }
10217     }
10218
10219     retVal = LoadGame(f, gameNumber, title, useList);
10220
10221     /* Make move registered during previous look at this game, if any */
10222     MakeRegisteredMove();
10223
10224     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10225         commentList[currentMove]
10226           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10227         DisplayComment(currentMove - 1, commentList[currentMove]);
10228     }
10229
10230     return retVal;
10231 }
10232
10233 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10234 int
10235 ReloadGame(offset)
10236      int offset;
10237 {
10238     int gameNumber = lastLoadGameNumber + offset;
10239     if (lastLoadGameFP == NULL) {
10240         DisplayError(_("No game has been loaded yet"), 0);
10241         return FALSE;
10242     }
10243     if (gameNumber <= 0) {
10244         DisplayError(_("Can't back up any further"), 0);
10245         return FALSE;
10246     }
10247     if (cmailMsgLoaded) {
10248         return CmailLoadGame(lastLoadGameFP, gameNumber,
10249                              lastLoadGameTitle, lastLoadGameUseList);
10250     } else {
10251         return LoadGame(lastLoadGameFP, gameNumber,
10252                         lastLoadGameTitle, lastLoadGameUseList);
10253     }
10254 }
10255
10256
10257
10258 /* Load the nth game from open file f */
10259 int
10260 LoadGame(f, gameNumber, title, useList)
10261      FILE *f;
10262      int gameNumber;
10263      char *title;
10264      int useList;
10265 {
10266     ChessMove cm;
10267     char buf[MSG_SIZ];
10268     int gn = gameNumber;
10269     ListGame *lg = NULL;
10270     int numPGNTags = 0;
10271     int err;
10272     GameMode oldGameMode;
10273     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10274
10275     if (appData.debugMode)
10276         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10277
10278     if (gameMode == Training )
10279         SetTrainingModeOff();
10280
10281     oldGameMode = gameMode;
10282     if (gameMode != BeginningOfGame) {
10283       Reset(FALSE, TRUE);
10284     }
10285
10286     gameFileFP = f;
10287     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10288         fclose(lastLoadGameFP);
10289     }
10290
10291     if (useList) {
10292         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10293
10294         if (lg) {
10295             fseek(f, lg->offset, 0);
10296             GameListHighlight(gameNumber);
10297             gn = 1;
10298         }
10299         else {
10300             DisplayError(_("Game number out of range"), 0);
10301             return FALSE;
10302         }
10303     } else {
10304         GameListDestroy();
10305         if (fseek(f, 0, 0) == -1) {
10306             if (f == lastLoadGameFP ?
10307                 gameNumber == lastLoadGameNumber + 1 :
10308                 gameNumber == 1) {
10309                 gn = 1;
10310             } else {
10311                 DisplayError(_("Can't seek on game file"), 0);
10312                 return FALSE;
10313             }
10314         }
10315     }
10316     lastLoadGameFP = f;
10317     lastLoadGameNumber = gameNumber;
10318     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10319     lastLoadGameUseList = useList;
10320
10321     yynewfile(f);
10322
10323     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10324       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10325                 lg->gameInfo.black);
10326             DisplayTitle(buf);
10327     } else if (*title != NULLCHAR) {
10328         if (gameNumber > 1) {
10329           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10330             DisplayTitle(buf);
10331         } else {
10332             DisplayTitle(title);
10333         }
10334     }
10335
10336     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10337         gameMode = PlayFromGameFile;
10338         ModeHighlight();
10339     }
10340
10341     currentMove = forwardMostMove = backwardMostMove = 0;
10342     CopyBoard(boards[0], initialPosition);
10343     StopClocks();
10344
10345     /*
10346      * Skip the first gn-1 games in the file.
10347      * Also skip over anything that precedes an identifiable
10348      * start of game marker, to avoid being confused by
10349      * garbage at the start of the file.  Currently
10350      * recognized start of game markers are the move number "1",
10351      * the pattern "gnuchess .* game", the pattern
10352      * "^[#;%] [^ ]* game file", and a PGN tag block.
10353      * A game that starts with one of the latter two patterns
10354      * will also have a move number 1, possibly
10355      * following a position diagram.
10356      * 5-4-02: Let's try being more lenient and allowing a game to
10357      * start with an unnumbered move.  Does that break anything?
10358      */
10359     cm = lastLoadGameStart = EndOfFile;
10360     while (gn > 0) {
10361         yyboardindex = forwardMostMove;
10362         cm = (ChessMove) Myylex();
10363         switch (cm) {
10364           case EndOfFile:
10365             if (cmailMsgLoaded) {
10366                 nCmailGames = CMAIL_MAX_GAMES - gn;
10367             } else {
10368                 Reset(TRUE, TRUE);
10369                 DisplayError(_("Game not found in file"), 0);
10370             }
10371             return FALSE;
10372
10373           case GNUChessGame:
10374           case XBoardGame:
10375             gn--;
10376             lastLoadGameStart = cm;
10377             break;
10378
10379           case MoveNumberOne:
10380             switch (lastLoadGameStart) {
10381               case GNUChessGame:
10382               case XBoardGame:
10383               case PGNTag:
10384                 break;
10385               case MoveNumberOne:
10386               case EndOfFile:
10387                 gn--;           /* count this game */
10388                 lastLoadGameStart = cm;
10389                 break;
10390               default:
10391                 /* impossible */
10392                 break;
10393             }
10394             break;
10395
10396           case PGNTag:
10397             switch (lastLoadGameStart) {
10398               case GNUChessGame:
10399               case PGNTag:
10400               case MoveNumberOne:
10401               case EndOfFile:
10402                 gn--;           /* count this game */
10403                 lastLoadGameStart = cm;
10404                 break;
10405               case XBoardGame:
10406                 lastLoadGameStart = cm; /* game counted already */
10407                 break;
10408               default:
10409                 /* impossible */
10410                 break;
10411             }
10412             if (gn > 0) {
10413                 do {
10414                     yyboardindex = forwardMostMove;
10415                     cm = (ChessMove) Myylex();
10416                 } while (cm == PGNTag || cm == Comment);
10417             }
10418             break;
10419
10420           case WhiteWins:
10421           case BlackWins:
10422           case GameIsDrawn:
10423             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10424                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10425                     != CMAIL_OLD_RESULT) {
10426                     nCmailResults ++ ;
10427                     cmailResult[  CMAIL_MAX_GAMES
10428                                 - gn - 1] = CMAIL_OLD_RESULT;
10429                 }
10430             }
10431             break;
10432
10433           case NormalMove:
10434             /* Only a NormalMove can be at the start of a game
10435              * without a position diagram. */
10436             if (lastLoadGameStart == EndOfFile ) {
10437               gn--;
10438               lastLoadGameStart = MoveNumberOne;
10439             }
10440             break;
10441
10442           default:
10443             break;
10444         }
10445     }
10446
10447     if (appData.debugMode)
10448       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10449
10450     if (cm == XBoardGame) {
10451         /* Skip any header junk before position diagram and/or move 1 */
10452         for (;;) {
10453             yyboardindex = forwardMostMove;
10454             cm = (ChessMove) Myylex();
10455
10456             if (cm == EndOfFile ||
10457                 cm == GNUChessGame || cm == XBoardGame) {
10458                 /* Empty game; pretend end-of-file and handle later */
10459                 cm = EndOfFile;
10460                 break;
10461             }
10462
10463             if (cm == MoveNumberOne || cm == PositionDiagram ||
10464                 cm == PGNTag || cm == Comment)
10465               break;
10466         }
10467     } else if (cm == GNUChessGame) {
10468         if (gameInfo.event != NULL) {
10469             free(gameInfo.event);
10470         }
10471         gameInfo.event = StrSave(yy_text);
10472     }
10473
10474     startedFromSetupPosition = FALSE;
10475     while (cm == PGNTag) {
10476         if (appData.debugMode)
10477           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10478         err = ParsePGNTag(yy_text, &gameInfo);
10479         if (!err) numPGNTags++;
10480
10481         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10482         if(gameInfo.variant != oldVariant) {
10483             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10484             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10485             InitPosition(TRUE);
10486             oldVariant = gameInfo.variant;
10487             if (appData.debugMode)
10488               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10489         }
10490
10491
10492         if (gameInfo.fen != NULL) {
10493           Board initial_position;
10494           startedFromSetupPosition = TRUE;
10495           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10496             Reset(TRUE, TRUE);
10497             DisplayError(_("Bad FEN position in file"), 0);
10498             return FALSE;
10499           }
10500           CopyBoard(boards[0], initial_position);
10501           if (blackPlaysFirst) {
10502             currentMove = forwardMostMove = backwardMostMove = 1;
10503             CopyBoard(boards[1], initial_position);
10504             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10505             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10506             timeRemaining[0][1] = whiteTimeRemaining;
10507             timeRemaining[1][1] = blackTimeRemaining;
10508             if (commentList[0] != NULL) {
10509               commentList[1] = commentList[0];
10510               commentList[0] = NULL;
10511             }
10512           } else {
10513             currentMove = forwardMostMove = backwardMostMove = 0;
10514           }
10515           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10516           {   int i;
10517               initialRulePlies = FENrulePlies;
10518               for( i=0; i< nrCastlingRights; i++ )
10519                   initialRights[i] = initial_position[CASTLING][i];
10520           }
10521           yyboardindex = forwardMostMove;
10522           free(gameInfo.fen);
10523           gameInfo.fen = NULL;
10524         }
10525
10526         yyboardindex = forwardMostMove;
10527         cm = (ChessMove) Myylex();
10528
10529         /* Handle comments interspersed among the tags */
10530         while (cm == Comment) {
10531             char *p;
10532             if (appData.debugMode)
10533               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10534             p = yy_text;
10535             AppendComment(currentMove, p, FALSE);
10536             yyboardindex = forwardMostMove;
10537             cm = (ChessMove) Myylex();
10538         }
10539     }
10540
10541     /* don't rely on existence of Event tag since if game was
10542      * pasted from clipboard the Event tag may not exist
10543      */
10544     if (numPGNTags > 0){
10545         char *tags;
10546         if (gameInfo.variant == VariantNormal) {
10547           VariantClass v = StringToVariant(gameInfo.event);
10548           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10549           if(v < VariantShogi) gameInfo.variant = v;
10550         }
10551         if (!matchMode) {
10552           if( appData.autoDisplayTags ) {
10553             tags = PGNTags(&gameInfo);
10554             TagsPopUp(tags, CmailMsg());
10555             free(tags);
10556           }
10557         }
10558     } else {
10559         /* Make something up, but don't display it now */
10560         SetGameInfo();
10561         TagsPopDown();
10562     }
10563
10564     if (cm == PositionDiagram) {
10565         int i, j;
10566         char *p;
10567         Board initial_position;
10568
10569         if (appData.debugMode)
10570           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10571
10572         if (!startedFromSetupPosition) {
10573             p = yy_text;
10574             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10575               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10576                 switch (*p) {
10577                   case '{':
10578                   case '[':
10579                   case '-':
10580                   case ' ':
10581                   case '\t':
10582                   case '\n':
10583                   case '\r':
10584                     break;
10585                   default:
10586                     initial_position[i][j++] = CharToPiece(*p);
10587                     break;
10588                 }
10589             while (*p == ' ' || *p == '\t' ||
10590                    *p == '\n' || *p == '\r') p++;
10591
10592             if (strncmp(p, "black", strlen("black"))==0)
10593               blackPlaysFirst = TRUE;
10594             else
10595               blackPlaysFirst = FALSE;
10596             startedFromSetupPosition = TRUE;
10597
10598             CopyBoard(boards[0], initial_position);
10599             if (blackPlaysFirst) {
10600                 currentMove = forwardMostMove = backwardMostMove = 1;
10601                 CopyBoard(boards[1], initial_position);
10602                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10603                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10604                 timeRemaining[0][1] = whiteTimeRemaining;
10605                 timeRemaining[1][1] = blackTimeRemaining;
10606                 if (commentList[0] != NULL) {
10607                     commentList[1] = commentList[0];
10608                     commentList[0] = NULL;
10609                 }
10610             } else {
10611                 currentMove = forwardMostMove = backwardMostMove = 0;
10612             }
10613         }
10614         yyboardindex = forwardMostMove;
10615         cm = (ChessMove) Myylex();
10616     }
10617
10618     if (first.pr == NoProc) {
10619         StartChessProgram(&first);
10620     }
10621     InitChessProgram(&first, FALSE);
10622     SendToProgram("force\n", &first);
10623     if (startedFromSetupPosition) {
10624         SendBoard(&first, forwardMostMove);
10625     if (appData.debugMode) {
10626         fprintf(debugFP, "Load Game\n");
10627     }
10628         DisplayBothClocks();
10629     }
10630
10631     /* [HGM] server: flag to write setup moves in broadcast file as one */
10632     loadFlag = appData.suppressLoadMoves;
10633
10634     while (cm == Comment) {
10635         char *p;
10636         if (appData.debugMode)
10637           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10638         p = yy_text;
10639         AppendComment(currentMove, p, FALSE);
10640         yyboardindex = forwardMostMove;
10641         cm = (ChessMove) Myylex();
10642     }
10643
10644     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10645         cm == WhiteWins || cm == BlackWins ||
10646         cm == GameIsDrawn || cm == GameUnfinished) {
10647         DisplayMessage("", _("No moves in game"));
10648         if (cmailMsgLoaded) {
10649             if (appData.debugMode)
10650               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10651             ClearHighlights();
10652             flipView = FALSE;
10653         }
10654         DrawPosition(FALSE, boards[currentMove]);
10655         DisplayBothClocks();
10656         gameMode = EditGame;
10657         ModeHighlight();
10658         gameFileFP = NULL;
10659         cmailOldMove = 0;
10660         return TRUE;
10661     }
10662
10663     // [HGM] PV info: routine tests if comment empty
10664     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10665         DisplayComment(currentMove - 1, commentList[currentMove]);
10666     }
10667     if (!matchMode && appData.timeDelay != 0)
10668       DrawPosition(FALSE, boards[currentMove]);
10669
10670     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10671       programStats.ok_to_send = 1;
10672     }
10673
10674     /* if the first token after the PGN tags is a move
10675      * and not move number 1, retrieve it from the parser
10676      */
10677     if (cm != MoveNumberOne)
10678         LoadGameOneMove(cm);
10679
10680     /* load the remaining moves from the file */
10681     while (LoadGameOneMove(EndOfFile)) {
10682       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10683       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10684     }
10685
10686     /* rewind to the start of the game */
10687     currentMove = backwardMostMove;
10688
10689     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10690
10691     if (oldGameMode == AnalyzeFile ||
10692         oldGameMode == AnalyzeMode) {
10693       AnalyzeFileEvent();
10694     }
10695
10696     if (matchMode || appData.timeDelay == 0) {
10697       ToEndEvent();
10698       gameMode = EditGame;
10699       ModeHighlight();
10700     } else if (appData.timeDelay > 0) {
10701       AutoPlayGameLoop();
10702     }
10703
10704     if (appData.debugMode)
10705         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10706
10707     loadFlag = 0; /* [HGM] true game starts */
10708     return TRUE;
10709 }
10710
10711 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10712 int
10713 ReloadPosition(offset)
10714      int offset;
10715 {
10716     int positionNumber = lastLoadPositionNumber + offset;
10717     if (lastLoadPositionFP == NULL) {
10718         DisplayError(_("No position has been loaded yet"), 0);
10719         return FALSE;
10720     }
10721     if (positionNumber <= 0) {
10722         DisplayError(_("Can't back up any further"), 0);
10723         return FALSE;
10724     }
10725     return LoadPosition(lastLoadPositionFP, positionNumber,
10726                         lastLoadPositionTitle);
10727 }
10728
10729 /* Load the nth position from the given file */
10730 int
10731 LoadPositionFromFile(filename, n, title)
10732      char *filename;
10733      int n;
10734      char *title;
10735 {
10736     FILE *f;
10737     char buf[MSG_SIZ];
10738
10739     if (strcmp(filename, "-") == 0) {
10740         return LoadPosition(stdin, n, "stdin");
10741     } else {
10742         f = fopen(filename, "rb");
10743         if (f == NULL) {
10744             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10745             DisplayError(buf, errno);
10746             return FALSE;
10747         } else {
10748             return LoadPosition(f, n, title);
10749         }
10750     }
10751 }
10752
10753 /* Load the nth position from the given open file, and close it */
10754 int
10755 LoadPosition(f, positionNumber, title)
10756      FILE *f;
10757      int positionNumber;
10758      char *title;
10759 {
10760     char *p, line[MSG_SIZ];
10761     Board initial_position;
10762     int i, j, fenMode, pn;
10763
10764     if (gameMode == Training )
10765         SetTrainingModeOff();
10766
10767     if (gameMode != BeginningOfGame) {
10768         Reset(FALSE, TRUE);
10769     }
10770     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10771         fclose(lastLoadPositionFP);
10772     }
10773     if (positionNumber == 0) positionNumber = 1;
10774     lastLoadPositionFP = f;
10775     lastLoadPositionNumber = positionNumber;
10776     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10777     if (first.pr == NoProc) {
10778       StartChessProgram(&first);
10779       InitChessProgram(&first, FALSE);
10780     }
10781     pn = positionNumber;
10782     if (positionNumber < 0) {
10783         /* Negative position number means to seek to that byte offset */
10784         if (fseek(f, -positionNumber, 0) == -1) {
10785             DisplayError(_("Can't seek on position file"), 0);
10786             return FALSE;
10787         };
10788         pn = 1;
10789     } else {
10790         if (fseek(f, 0, 0) == -1) {
10791             if (f == lastLoadPositionFP ?
10792                 positionNumber == lastLoadPositionNumber + 1 :
10793                 positionNumber == 1) {
10794                 pn = 1;
10795             } else {
10796                 DisplayError(_("Can't seek on position file"), 0);
10797                 return FALSE;
10798             }
10799         }
10800     }
10801     /* See if this file is FEN or old-style xboard */
10802     if (fgets(line, MSG_SIZ, f) == NULL) {
10803         DisplayError(_("Position not found in file"), 0);
10804         return FALSE;
10805     }
10806     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10807     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10808
10809     if (pn >= 2) {
10810         if (fenMode || line[0] == '#') pn--;
10811         while (pn > 0) {
10812             /* skip positions before number pn */
10813             if (fgets(line, MSG_SIZ, f) == NULL) {
10814                 Reset(TRUE, TRUE);
10815                 DisplayError(_("Position not found in file"), 0);
10816                 return FALSE;
10817             }
10818             if (fenMode || line[0] == '#') pn--;
10819         }
10820     }
10821
10822     if (fenMode) {
10823         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10824             DisplayError(_("Bad FEN position in file"), 0);
10825             return FALSE;
10826         }
10827     } else {
10828         (void) fgets(line, MSG_SIZ, f);
10829         (void) fgets(line, MSG_SIZ, f);
10830
10831         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10832             (void) fgets(line, MSG_SIZ, f);
10833             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10834                 if (*p == ' ')
10835                   continue;
10836                 initial_position[i][j++] = CharToPiece(*p);
10837             }
10838         }
10839
10840         blackPlaysFirst = FALSE;
10841         if (!feof(f)) {
10842             (void) fgets(line, MSG_SIZ, f);
10843             if (strncmp(line, "black", strlen("black"))==0)
10844               blackPlaysFirst = TRUE;
10845         }
10846     }
10847     startedFromSetupPosition = TRUE;
10848
10849     SendToProgram("force\n", &first);
10850     CopyBoard(boards[0], initial_position);
10851     if (blackPlaysFirst) {
10852         currentMove = forwardMostMove = backwardMostMove = 1;
10853         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10854         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10855         CopyBoard(boards[1], initial_position);
10856         DisplayMessage("", _("Black to play"));
10857     } else {
10858         currentMove = forwardMostMove = backwardMostMove = 0;
10859         DisplayMessage("", _("White to play"));
10860     }
10861     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10862     SendBoard(&first, forwardMostMove);
10863     if (appData.debugMode) {
10864 int i, j;
10865   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10866   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10867         fprintf(debugFP, "Load Position\n");
10868     }
10869
10870     if (positionNumber > 1) {
10871       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10872         DisplayTitle(line);
10873     } else {
10874         DisplayTitle(title);
10875     }
10876     gameMode = EditGame;
10877     ModeHighlight();
10878     ResetClocks();
10879     timeRemaining[0][1] = whiteTimeRemaining;
10880     timeRemaining[1][1] = blackTimeRemaining;
10881     DrawPosition(FALSE, boards[currentMove]);
10882
10883     return TRUE;
10884 }
10885
10886
10887 void
10888 CopyPlayerNameIntoFileName(dest, src)
10889      char **dest, *src;
10890 {
10891     while (*src != NULLCHAR && *src != ',') {
10892         if (*src == ' ') {
10893             *(*dest)++ = '_';
10894             src++;
10895         } else {
10896             *(*dest)++ = *src++;
10897         }
10898     }
10899 }
10900
10901 char *DefaultFileName(ext)
10902      char *ext;
10903 {
10904     static char def[MSG_SIZ];
10905     char *p;
10906
10907     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10908         p = def;
10909         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10910         *p++ = '-';
10911         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10912         *p++ = '.';
10913         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10914     } else {
10915         def[0] = NULLCHAR;
10916     }
10917     return def;
10918 }
10919
10920 /* Save the current game to the given file */
10921 int
10922 SaveGameToFile(filename, append)
10923      char *filename;
10924      int append;
10925 {
10926     FILE *f;
10927     char buf[MSG_SIZ];
10928
10929     if (strcmp(filename, "-") == 0) {
10930         return SaveGame(stdout, 0, NULL);
10931     } else {
10932         f = fopen(filename, append ? "a" : "w");
10933         if (f == NULL) {
10934             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10935             DisplayError(buf, errno);
10936             return FALSE;
10937         } else {
10938             return SaveGame(f, 0, NULL);
10939         }
10940     }
10941 }
10942
10943 char *
10944 SavePart(str)
10945      char *str;
10946 {
10947     static char buf[MSG_SIZ];
10948     char *p;
10949
10950     p = strchr(str, ' ');
10951     if (p == NULL) return str;
10952     strncpy(buf, str, p - str);
10953     buf[p - str] = NULLCHAR;
10954     return buf;
10955 }
10956
10957 #define PGN_MAX_LINE 75
10958
10959 #define PGN_SIDE_WHITE  0
10960 #define PGN_SIDE_BLACK  1
10961
10962 /* [AS] */
10963 static int FindFirstMoveOutOfBook( int side )
10964 {
10965     int result = -1;
10966
10967     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10968         int index = backwardMostMove;
10969         int has_book_hit = 0;
10970
10971         if( (index % 2) != side ) {
10972             index++;
10973         }
10974
10975         while( index < forwardMostMove ) {
10976             /* Check to see if engine is in book */
10977             int depth = pvInfoList[index].depth;
10978             int score = pvInfoList[index].score;
10979             int in_book = 0;
10980
10981             if( depth <= 2 ) {
10982                 in_book = 1;
10983             }
10984             else if( score == 0 && depth == 63 ) {
10985                 in_book = 1; /* Zappa */
10986             }
10987             else if( score == 2 && depth == 99 ) {
10988                 in_book = 1; /* Abrok */
10989             }
10990
10991             has_book_hit += in_book;
10992
10993             if( ! in_book ) {
10994                 result = index;
10995
10996                 break;
10997             }
10998
10999             index += 2;
11000         }
11001     }
11002
11003     return result;
11004 }
11005
11006 /* [AS] */
11007 void GetOutOfBookInfo( char * buf )
11008 {
11009     int oob[2];
11010     int i;
11011     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11012
11013     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11014     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11015
11016     *buf = '\0';
11017
11018     if( oob[0] >= 0 || oob[1] >= 0 ) {
11019         for( i=0; i<2; i++ ) {
11020             int idx = oob[i];
11021
11022             if( idx >= 0 ) {
11023                 if( i > 0 && oob[0] >= 0 ) {
11024                     strcat( buf, "   " );
11025                 }
11026
11027                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11028                 sprintf( buf+strlen(buf), "%s%.2f",
11029                     pvInfoList[idx].score >= 0 ? "+" : "",
11030                     pvInfoList[idx].score / 100.0 );
11031             }
11032         }
11033     }
11034 }
11035
11036 /* Save game in PGN style and close the file */
11037 int
11038 SaveGamePGN(f)
11039      FILE *f;
11040 {
11041     int i, offset, linelen, newblock;
11042     time_t tm;
11043 //    char *movetext;
11044     char numtext[32];
11045     int movelen, numlen, blank;
11046     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11047
11048     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11049
11050     tm = time((time_t *) NULL);
11051
11052     PrintPGNTags(f, &gameInfo);
11053
11054     if (backwardMostMove > 0 || startedFromSetupPosition) {
11055         char *fen = PositionToFEN(backwardMostMove, NULL);
11056         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11057         fprintf(f, "\n{--------------\n");
11058         PrintPosition(f, backwardMostMove);
11059         fprintf(f, "--------------}\n");
11060         free(fen);
11061     }
11062     else {
11063         /* [AS] Out of book annotation */
11064         if( appData.saveOutOfBookInfo ) {
11065             char buf[64];
11066
11067             GetOutOfBookInfo( buf );
11068
11069             if( buf[0] != '\0' ) {
11070                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11071             }
11072         }
11073
11074         fprintf(f, "\n");
11075     }
11076
11077     i = backwardMostMove;
11078     linelen = 0;
11079     newblock = TRUE;
11080
11081     while (i < forwardMostMove) {
11082         /* Print comments preceding this move */
11083         if (commentList[i] != NULL) {
11084             if (linelen > 0) fprintf(f, "\n");
11085             fprintf(f, "%s", commentList[i]);
11086             linelen = 0;
11087             newblock = TRUE;
11088         }
11089
11090         /* Format move number */
11091         if ((i % 2) == 0)
11092           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11093         else
11094           if (newblock)
11095             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11096           else
11097             numtext[0] = NULLCHAR;
11098
11099         numlen = strlen(numtext);
11100         newblock = FALSE;
11101
11102         /* Print move number */
11103         blank = linelen > 0 && numlen > 0;
11104         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11105             fprintf(f, "\n");
11106             linelen = 0;
11107             blank = 0;
11108         }
11109         if (blank) {
11110             fprintf(f, " ");
11111             linelen++;
11112         }
11113         fprintf(f, "%s", numtext);
11114         linelen += numlen;
11115
11116         /* Get move */
11117         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11118         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11119
11120         /* Print move */
11121         blank = linelen > 0 && movelen > 0;
11122         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11123             fprintf(f, "\n");
11124             linelen = 0;
11125             blank = 0;
11126         }
11127         if (blank) {
11128             fprintf(f, " ");
11129             linelen++;
11130         }
11131         fprintf(f, "%s", move_buffer);
11132         linelen += movelen;
11133
11134         /* [AS] Add PV info if present */
11135         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11136             /* [HGM] add time */
11137             char buf[MSG_SIZ]; int seconds;
11138
11139             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11140
11141             if( seconds <= 0)
11142               buf[0] = 0;
11143             else
11144               if( seconds < 30 )
11145                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11146               else
11147                 {
11148                   seconds = (seconds + 4)/10; // round to full seconds
11149                   if( seconds < 60 )
11150                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11151                   else
11152                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11153                 }
11154
11155             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11156                       pvInfoList[i].score >= 0 ? "+" : "",
11157                       pvInfoList[i].score / 100.0,
11158                       pvInfoList[i].depth,
11159                       buf );
11160
11161             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11162
11163             /* Print score/depth */
11164             blank = linelen > 0 && movelen > 0;
11165             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11166                 fprintf(f, "\n");
11167                 linelen = 0;
11168                 blank = 0;
11169             }
11170             if (blank) {
11171                 fprintf(f, " ");
11172                 linelen++;
11173             }
11174             fprintf(f, "%s", move_buffer);
11175             linelen += movelen;
11176         }
11177
11178         i++;
11179     }
11180
11181     /* Start a new line */
11182     if (linelen > 0) fprintf(f, "\n");
11183
11184     /* Print comments after last move */
11185     if (commentList[i] != NULL) {
11186         fprintf(f, "%s\n", commentList[i]);
11187     }
11188
11189     /* Print result */
11190     if (gameInfo.resultDetails != NULL &&
11191         gameInfo.resultDetails[0] != NULLCHAR) {
11192         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11193                 PGNResult(gameInfo.result));
11194     } else {
11195         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11196     }
11197
11198     fclose(f);
11199     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11200     return TRUE;
11201 }
11202
11203 /* Save game in old style and close the file */
11204 int
11205 SaveGameOldStyle(f)
11206      FILE *f;
11207 {
11208     int i, offset;
11209     time_t tm;
11210
11211     tm = time((time_t *) NULL);
11212
11213     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11214     PrintOpponents(f);
11215
11216     if (backwardMostMove > 0 || startedFromSetupPosition) {
11217         fprintf(f, "\n[--------------\n");
11218         PrintPosition(f, backwardMostMove);
11219         fprintf(f, "--------------]\n");
11220     } else {
11221         fprintf(f, "\n");
11222     }
11223
11224     i = backwardMostMove;
11225     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11226
11227     while (i < forwardMostMove) {
11228         if (commentList[i] != NULL) {
11229             fprintf(f, "[%s]\n", commentList[i]);
11230         }
11231
11232         if ((i % 2) == 1) {
11233             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11234             i++;
11235         } else {
11236             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11237             i++;
11238             if (commentList[i] != NULL) {
11239                 fprintf(f, "\n");
11240                 continue;
11241             }
11242             if (i >= forwardMostMove) {
11243                 fprintf(f, "\n");
11244                 break;
11245             }
11246             fprintf(f, "%s\n", parseList[i]);
11247             i++;
11248         }
11249     }
11250
11251     if (commentList[i] != NULL) {
11252         fprintf(f, "[%s]\n", commentList[i]);
11253     }
11254
11255     /* This isn't really the old style, but it's close enough */
11256     if (gameInfo.resultDetails != NULL &&
11257         gameInfo.resultDetails[0] != NULLCHAR) {
11258         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11259                 gameInfo.resultDetails);
11260     } else {
11261         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11262     }
11263
11264     fclose(f);
11265     return TRUE;
11266 }
11267
11268 /* Save the current game to open file f and close the file */
11269 int
11270 SaveGame(f, dummy, dummy2)
11271      FILE *f;
11272      int dummy;
11273      char *dummy2;
11274 {
11275     if (gameMode == EditPosition) EditPositionDone(TRUE);
11276     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11277     if (appData.oldSaveStyle)
11278       return SaveGameOldStyle(f);
11279     else
11280       return SaveGamePGN(f);
11281 }
11282
11283 /* Save the current position to the given file */
11284 int
11285 SavePositionToFile(filename)
11286      char *filename;
11287 {
11288     FILE *f;
11289     char buf[MSG_SIZ];
11290
11291     if (strcmp(filename, "-") == 0) {
11292         return SavePosition(stdout, 0, NULL);
11293     } else {
11294         f = fopen(filename, "a");
11295         if (f == NULL) {
11296             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11297             DisplayError(buf, errno);
11298             return FALSE;
11299         } else {
11300             SavePosition(f, 0, NULL);
11301             return TRUE;
11302         }
11303     }
11304 }
11305
11306 /* Save the current position to the given open file and close the file */
11307 int
11308 SavePosition(f, dummy, dummy2)
11309      FILE *f;
11310      int dummy;
11311      char *dummy2;
11312 {
11313     time_t tm;
11314     char *fen;
11315
11316     if (gameMode == EditPosition) EditPositionDone(TRUE);
11317     if (appData.oldSaveStyle) {
11318         tm = time((time_t *) NULL);
11319
11320         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11321         PrintOpponents(f);
11322         fprintf(f, "[--------------\n");
11323         PrintPosition(f, currentMove);
11324         fprintf(f, "--------------]\n");
11325     } else {
11326         fen = PositionToFEN(currentMove, NULL);
11327         fprintf(f, "%s\n", fen);
11328         free(fen);
11329     }
11330     fclose(f);
11331     return TRUE;
11332 }
11333
11334 void
11335 ReloadCmailMsgEvent(unregister)
11336      int unregister;
11337 {
11338 #if !WIN32
11339     static char *inFilename = NULL;
11340     static char *outFilename;
11341     int i;
11342     struct stat inbuf, outbuf;
11343     int status;
11344
11345     /* Any registered moves are unregistered if unregister is set, */
11346     /* i.e. invoked by the signal handler */
11347     if (unregister) {
11348         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11349             cmailMoveRegistered[i] = FALSE;
11350             if (cmailCommentList[i] != NULL) {
11351                 free(cmailCommentList[i]);
11352                 cmailCommentList[i] = NULL;
11353             }
11354         }
11355         nCmailMovesRegistered = 0;
11356     }
11357
11358     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11359         cmailResult[i] = CMAIL_NOT_RESULT;
11360     }
11361     nCmailResults = 0;
11362
11363     if (inFilename == NULL) {
11364         /* Because the filenames are static they only get malloced once  */
11365         /* and they never get freed                                      */
11366         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11367         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11368
11369         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11370         sprintf(outFilename, "%s.out", appData.cmailGameName);
11371     }
11372
11373     status = stat(outFilename, &outbuf);
11374     if (status < 0) {
11375         cmailMailedMove = FALSE;
11376     } else {
11377         status = stat(inFilename, &inbuf);
11378         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11379     }
11380
11381     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11382        counts the games, notes how each one terminated, etc.
11383
11384        It would be nice to remove this kludge and instead gather all
11385        the information while building the game list.  (And to keep it
11386        in the game list nodes instead of having a bunch of fixed-size
11387        parallel arrays.)  Note this will require getting each game's
11388        termination from the PGN tags, as the game list builder does
11389        not process the game moves.  --mann
11390        */
11391     cmailMsgLoaded = TRUE;
11392     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11393
11394     /* Load first game in the file or popup game menu */
11395     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11396
11397 #endif /* !WIN32 */
11398     return;
11399 }
11400
11401 int
11402 RegisterMove()
11403 {
11404     FILE *f;
11405     char string[MSG_SIZ];
11406
11407     if (   cmailMailedMove
11408         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11409         return TRUE;            /* Allow free viewing  */
11410     }
11411
11412     /* Unregister move to ensure that we don't leave RegisterMove        */
11413     /* with the move registered when the conditions for registering no   */
11414     /* longer hold                                                       */
11415     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11416         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11417         nCmailMovesRegistered --;
11418
11419         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11420           {
11421               free(cmailCommentList[lastLoadGameNumber - 1]);
11422               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11423           }
11424     }
11425
11426     if (cmailOldMove == -1) {
11427         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11428         return FALSE;
11429     }
11430
11431     if (currentMove > cmailOldMove + 1) {
11432         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11433         return FALSE;
11434     }
11435
11436     if (currentMove < cmailOldMove) {
11437         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11438         return FALSE;
11439     }
11440
11441     if (forwardMostMove > currentMove) {
11442         /* Silently truncate extra moves */
11443         TruncateGame();
11444     }
11445
11446     if (   (currentMove == cmailOldMove + 1)
11447         || (   (currentMove == cmailOldMove)
11448             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11449                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11450         if (gameInfo.result != GameUnfinished) {
11451             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11452         }
11453
11454         if (commentList[currentMove] != NULL) {
11455             cmailCommentList[lastLoadGameNumber - 1]
11456               = StrSave(commentList[currentMove]);
11457         }
11458         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11459
11460         if (appData.debugMode)
11461           fprintf(debugFP, "Saving %s for game %d\n",
11462                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11463
11464         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11465
11466         f = fopen(string, "w");
11467         if (appData.oldSaveStyle) {
11468             SaveGameOldStyle(f); /* also closes the file */
11469
11470             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11471             f = fopen(string, "w");
11472             SavePosition(f, 0, NULL); /* also closes the file */
11473         } else {
11474             fprintf(f, "{--------------\n");
11475             PrintPosition(f, currentMove);
11476             fprintf(f, "--------------}\n\n");
11477
11478             SaveGame(f, 0, NULL); /* also closes the file*/
11479         }
11480
11481         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11482         nCmailMovesRegistered ++;
11483     } else if (nCmailGames == 1) {
11484         DisplayError(_("You have not made a move yet"), 0);
11485         return FALSE;
11486     }
11487
11488     return TRUE;
11489 }
11490
11491 void
11492 MailMoveEvent()
11493 {
11494 #if !WIN32
11495     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11496     FILE *commandOutput;
11497     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11498     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11499     int nBuffers;
11500     int i;
11501     int archived;
11502     char *arcDir;
11503
11504     if (! cmailMsgLoaded) {
11505         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11506         return;
11507     }
11508
11509     if (nCmailGames == nCmailResults) {
11510         DisplayError(_("No unfinished games"), 0);
11511         return;
11512     }
11513
11514 #if CMAIL_PROHIBIT_REMAIL
11515     if (cmailMailedMove) {
11516       snprintf(msg, MSG_SIZ, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
11517         DisplayError(msg, 0);
11518         return;
11519     }
11520 #endif
11521
11522     if (! (cmailMailedMove || RegisterMove())) return;
11523
11524     if (   cmailMailedMove
11525         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11526       snprintf(string, MSG_SIZ, partCommandString,
11527                appData.debugMode ? " -v" : "", appData.cmailGameName);
11528         commandOutput = popen(string, "r");
11529
11530         if (commandOutput == NULL) {
11531             DisplayError(_("Failed to invoke cmail"), 0);
11532         } else {
11533             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11534                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11535             }
11536             if (nBuffers > 1) {
11537                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11538                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11539                 nBytes = MSG_SIZ - 1;
11540             } else {
11541                 (void) memcpy(msg, buffer, nBytes);
11542             }
11543             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11544
11545             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11546                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11547
11548                 archived = TRUE;
11549                 for (i = 0; i < nCmailGames; i ++) {
11550                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11551                         archived = FALSE;
11552                     }
11553                 }
11554                 if (   archived
11555                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11556                         != NULL)) {
11557                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11558                            arcDir,
11559                            appData.cmailGameName,
11560                            gameInfo.date);
11561                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11562                     cmailMsgLoaded = FALSE;
11563                 }
11564             }
11565
11566             DisplayInformation(msg);
11567             pclose(commandOutput);
11568         }
11569     } else {
11570         if ((*cmailMsg) != '\0') {
11571             DisplayInformation(cmailMsg);
11572         }
11573     }
11574
11575     return;
11576 #endif /* !WIN32 */
11577 }
11578
11579 char *
11580 CmailMsg()
11581 {
11582 #if WIN32
11583     return NULL;
11584 #else
11585     int  prependComma = 0;
11586     char number[5];
11587     char string[MSG_SIZ];       /* Space for game-list */
11588     int  i;
11589
11590     if (!cmailMsgLoaded) return "";
11591
11592     if (cmailMailedMove) {
11593       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11594     } else {
11595         /* Create a list of games left */
11596       snprintf(string, MSG_SIZ, "[");
11597         for (i = 0; i < nCmailGames; i ++) {
11598             if (! (   cmailMoveRegistered[i]
11599                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11600                 if (prependComma) {
11601                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11602                 } else {
11603                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11604                     prependComma = 1;
11605                 }
11606
11607                 strcat(string, number);
11608             }
11609         }
11610         strcat(string, "]");
11611
11612         if (nCmailMovesRegistered + nCmailResults == 0) {
11613             switch (nCmailGames) {
11614               case 1:
11615                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11616                 break;
11617
11618               case 2:
11619                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11620                 break;
11621
11622               default:
11623                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11624                          nCmailGames);
11625                 break;
11626             }
11627         } else {
11628             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11629               case 1:
11630                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11631                          string);
11632                 break;
11633
11634               case 0:
11635                 if (nCmailResults == nCmailGames) {
11636                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11637                 } else {
11638                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11639                 }
11640                 break;
11641
11642               default:
11643                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11644                          string);
11645             }
11646         }
11647     }
11648     return cmailMsg;
11649 #endif /* WIN32 */
11650 }
11651
11652 void
11653 ResetGameEvent()
11654 {
11655     if (gameMode == Training)
11656       SetTrainingModeOff();
11657
11658     Reset(TRUE, TRUE);
11659     cmailMsgLoaded = FALSE;
11660     if (appData.icsActive) {
11661       SendToICS(ics_prefix);
11662       SendToICS("refresh\n");
11663     }
11664 }
11665
11666 void
11667 ExitEvent(status)
11668      int status;
11669 {
11670     exiting++;
11671     if (exiting > 2) {
11672       /* Give up on clean exit */
11673       exit(status);
11674     }
11675     if (exiting > 1) {
11676       /* Keep trying for clean exit */
11677       return;
11678     }
11679
11680     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11681
11682     if (telnetISR != NULL) {
11683       RemoveInputSource(telnetISR);
11684     }
11685     if (icsPR != NoProc) {
11686       DestroyChildProcess(icsPR, TRUE);
11687     }
11688
11689     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11690     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11691
11692     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11693     /* make sure this other one finishes before killing it!                  */
11694     if(endingGame) { int count = 0;
11695         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11696         while(endingGame && count++ < 10) DoSleep(1);
11697         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11698     }
11699
11700     /* Kill off chess programs */
11701     if (first.pr != NoProc) {
11702         ExitAnalyzeMode();
11703
11704         DoSleep( appData.delayBeforeQuit );
11705         SendToProgram("quit\n", &first);
11706         DoSleep( appData.delayAfterQuit );
11707         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11708     }
11709     if (second.pr != NoProc) {
11710         DoSleep( appData.delayBeforeQuit );
11711         SendToProgram("quit\n", &second);
11712         DoSleep( appData.delayAfterQuit );
11713         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11714     }
11715     if (first.isr != NULL) {
11716         RemoveInputSource(first.isr);
11717     }
11718     if (second.isr != NULL) {
11719         RemoveInputSource(second.isr);
11720     }
11721
11722     ShutDownFrontEnd();
11723     exit(status);
11724 }
11725
11726 void
11727 PauseEvent()
11728 {
11729     if (appData.debugMode)
11730         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11731     if (pausing) {
11732         pausing = FALSE;
11733         ModeHighlight();
11734         if (gameMode == MachinePlaysWhite ||
11735             gameMode == MachinePlaysBlack) {
11736             StartClocks();
11737         } else {
11738             DisplayBothClocks();
11739         }
11740         if (gameMode == PlayFromGameFile) {
11741             if (appData.timeDelay >= 0)
11742                 AutoPlayGameLoop();
11743         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11744             Reset(FALSE, TRUE);
11745             SendToICS(ics_prefix);
11746             SendToICS("refresh\n");
11747         } else if (currentMove < forwardMostMove) {
11748             ForwardInner(forwardMostMove);
11749         }
11750         pauseExamInvalid = FALSE;
11751     } else {
11752         switch (gameMode) {
11753           default:
11754             return;
11755           case IcsExamining:
11756             pauseExamForwardMostMove = forwardMostMove;
11757             pauseExamInvalid = FALSE;
11758             /* fall through */
11759           case IcsObserving:
11760           case IcsPlayingWhite:
11761           case IcsPlayingBlack:
11762             pausing = TRUE;
11763             ModeHighlight();
11764             return;
11765           case PlayFromGameFile:
11766             (void) StopLoadGameTimer();
11767             pausing = TRUE;
11768             ModeHighlight();
11769             break;
11770           case BeginningOfGame:
11771             if (appData.icsActive) return;
11772             /* else fall through */
11773           case MachinePlaysWhite:
11774           case MachinePlaysBlack:
11775           case TwoMachinesPlay:
11776             if (forwardMostMove == 0)
11777               return;           /* don't pause if no one has moved */
11778             if ((gameMode == MachinePlaysWhite &&
11779                  !WhiteOnMove(forwardMostMove)) ||
11780                 (gameMode == MachinePlaysBlack &&
11781                  WhiteOnMove(forwardMostMove))) {
11782                 StopClocks();
11783             }
11784             pausing = TRUE;
11785             ModeHighlight();
11786             break;
11787         }
11788     }
11789 }
11790
11791 void
11792 EditCommentEvent()
11793 {
11794     char title[MSG_SIZ];
11795
11796     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11797       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11798     } else {
11799       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11800                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11801                parseList[currentMove - 1]);
11802     }
11803
11804     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11805 }
11806
11807
11808 void
11809 EditTagsEvent()
11810 {
11811     char *tags = PGNTags(&gameInfo);
11812     EditTagsPopUp(tags, NULL);
11813     free(tags);
11814 }
11815
11816 void
11817 AnalyzeModeEvent()
11818 {
11819     if (appData.noChessProgram || gameMode == AnalyzeMode)
11820       return;
11821
11822     if (gameMode != AnalyzeFile) {
11823         if (!appData.icsEngineAnalyze) {
11824                EditGameEvent();
11825                if (gameMode != EditGame) return;
11826         }
11827         ResurrectChessProgram();
11828         SendToProgram("analyze\n", &first);
11829         first.analyzing = TRUE;
11830         /*first.maybeThinking = TRUE;*/
11831         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11832         EngineOutputPopUp();
11833     }
11834     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11835     pausing = FALSE;
11836     ModeHighlight();
11837     SetGameInfo();
11838
11839     StartAnalysisClock();
11840     GetTimeMark(&lastNodeCountTime);
11841     lastNodeCount = 0;
11842 }
11843
11844 void
11845 AnalyzeFileEvent()
11846 {
11847     if (appData.noChessProgram || gameMode == AnalyzeFile)
11848       return;
11849
11850     if (gameMode != AnalyzeMode) {
11851         EditGameEvent();
11852         if (gameMode != EditGame) return;
11853         ResurrectChessProgram();
11854         SendToProgram("analyze\n", &first);
11855         first.analyzing = TRUE;
11856         /*first.maybeThinking = TRUE;*/
11857         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11858         EngineOutputPopUp();
11859     }
11860     gameMode = AnalyzeFile;
11861     pausing = FALSE;
11862     ModeHighlight();
11863     SetGameInfo();
11864
11865     StartAnalysisClock();
11866     GetTimeMark(&lastNodeCountTime);
11867     lastNodeCount = 0;
11868 }
11869
11870 void
11871 MachineWhiteEvent()
11872 {
11873     char buf[MSG_SIZ];
11874     char *bookHit = NULL;
11875
11876     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11877       return;
11878
11879
11880     if (gameMode == PlayFromGameFile ||
11881         gameMode == TwoMachinesPlay  ||
11882         gameMode == Training         ||
11883         gameMode == AnalyzeMode      ||
11884         gameMode == EndOfGame)
11885         EditGameEvent();
11886
11887     if (gameMode == EditPosition)
11888         EditPositionDone(TRUE);
11889
11890     if (!WhiteOnMove(currentMove)) {
11891         DisplayError(_("It is not White's turn"), 0);
11892         return;
11893     }
11894
11895     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11896       ExitAnalyzeMode();
11897
11898     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11899         gameMode == AnalyzeFile)
11900         TruncateGame();
11901
11902     ResurrectChessProgram();    /* in case it isn't running */
11903     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11904         gameMode = MachinePlaysWhite;
11905         ResetClocks();
11906     } else
11907     gameMode = MachinePlaysWhite;
11908     pausing = FALSE;
11909     ModeHighlight();
11910     SetGameInfo();
11911     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11912     DisplayTitle(buf);
11913     if (first.sendName) {
11914       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11915       SendToProgram(buf, &first);
11916     }
11917     if (first.sendTime) {
11918       if (first.useColors) {
11919         SendToProgram("black\n", &first); /*gnu kludge*/
11920       }
11921       SendTimeRemaining(&first, TRUE);
11922     }
11923     if (first.useColors) {
11924       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11925     }
11926     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11927     SetMachineThinkingEnables();
11928     first.maybeThinking = TRUE;
11929     StartClocks();
11930     firstMove = FALSE;
11931
11932     if (appData.autoFlipView && !flipView) {
11933       flipView = !flipView;
11934       DrawPosition(FALSE, NULL);
11935       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11936     }
11937
11938     if(bookHit) { // [HGM] book: simulate book reply
11939         static char bookMove[MSG_SIZ]; // a bit generous?
11940
11941         programStats.nodes = programStats.depth = programStats.time =
11942         programStats.score = programStats.got_only_move = 0;
11943         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11944
11945         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11946         strcat(bookMove, bookHit);
11947         HandleMachineMove(bookMove, &first);
11948     }
11949 }
11950
11951 void
11952 MachineBlackEvent()
11953 {
11954   char buf[MSG_SIZ];
11955   char *bookHit = NULL;
11956
11957     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11958         return;
11959
11960
11961     if (gameMode == PlayFromGameFile ||
11962         gameMode == TwoMachinesPlay  ||
11963         gameMode == Training         ||
11964         gameMode == AnalyzeMode      ||
11965         gameMode == EndOfGame)
11966         EditGameEvent();
11967
11968     if (gameMode == EditPosition)
11969         EditPositionDone(TRUE);
11970
11971     if (WhiteOnMove(currentMove)) {
11972         DisplayError(_("It is not Black's turn"), 0);
11973         return;
11974     }
11975
11976     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11977       ExitAnalyzeMode();
11978
11979     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11980         gameMode == AnalyzeFile)
11981         TruncateGame();
11982
11983     ResurrectChessProgram();    /* in case it isn't running */
11984     gameMode = MachinePlaysBlack;
11985     pausing = FALSE;
11986     ModeHighlight();
11987     SetGameInfo();
11988     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11989     DisplayTitle(buf);
11990     if (first.sendName) {
11991       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11992       SendToProgram(buf, &first);
11993     }
11994     if (first.sendTime) {
11995       if (first.useColors) {
11996         SendToProgram("white\n", &first); /*gnu kludge*/
11997       }
11998       SendTimeRemaining(&first, FALSE);
11999     }
12000     if (first.useColors) {
12001       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12002     }
12003     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12004     SetMachineThinkingEnables();
12005     first.maybeThinking = TRUE;
12006     StartClocks();
12007
12008     if (appData.autoFlipView && flipView) {
12009       flipView = !flipView;
12010       DrawPosition(FALSE, NULL);
12011       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12012     }
12013     if(bookHit) { // [HGM] book: simulate book reply
12014         static char bookMove[MSG_SIZ]; // a bit generous?
12015
12016         programStats.nodes = programStats.depth = programStats.time =
12017         programStats.score = programStats.got_only_move = 0;
12018         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12019
12020         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12021         strcat(bookMove, bookHit);
12022         HandleMachineMove(bookMove, &first);
12023     }
12024 }
12025
12026
12027 void
12028 DisplayTwoMachinesTitle()
12029 {
12030     char buf[MSG_SIZ];
12031     if (appData.matchGames > 0) {
12032         if (first.twoMachinesColor[0] == 'w') {
12033           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12034                    gameInfo.white, gameInfo.black,
12035                    first.matchWins, second.matchWins,
12036                    matchGame - 1 - (first.matchWins + second.matchWins));
12037         } else {
12038           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12039                    gameInfo.white, gameInfo.black,
12040                    second.matchWins, first.matchWins,
12041                    matchGame - 1 - (first.matchWins + second.matchWins));
12042         }
12043     } else {
12044       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12045     }
12046     DisplayTitle(buf);
12047 }
12048
12049 void
12050 SettingsMenuIfReady()
12051 {
12052   if (second.lastPing != second.lastPong) {
12053     DisplayMessage("", _("Waiting for second chess program"));
12054     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12055     return;
12056   }
12057   ThawUI();
12058   DisplayMessage("", "");
12059   SettingsPopUp(&second);
12060 }
12061
12062 int
12063 WaitForSecond(DelayedEventCallback retry)
12064 {
12065     if (second.pr == NULL) {
12066         StartChessProgram(&second);
12067         if (second.protocolVersion == 1) {
12068           retry();
12069         } else {
12070           /* kludge: allow timeout for initial "feature" command */
12071           FreezeUI();
12072           DisplayMessage("", _("Starting second chess program"));
12073           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12074         }
12075         return 1;
12076     }
12077     return 0;
12078 }
12079
12080 void
12081 TwoMachinesEvent P((void))
12082 {
12083     int i;
12084     char buf[MSG_SIZ];
12085     ChessProgramState *onmove;
12086     char *bookHit = NULL;
12087
12088     if (appData.noChessProgram) return;
12089
12090     switch (gameMode) {
12091       case TwoMachinesPlay:
12092         return;
12093       case MachinePlaysWhite:
12094       case MachinePlaysBlack:
12095         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12096             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12097             return;
12098         }
12099         /* fall through */
12100       case BeginningOfGame:
12101       case PlayFromGameFile:
12102       case EndOfGame:
12103         EditGameEvent();
12104         if (gameMode != EditGame) return;
12105         break;
12106       case EditPosition:
12107         EditPositionDone(TRUE);
12108         break;
12109       case AnalyzeMode:
12110       case AnalyzeFile:
12111         ExitAnalyzeMode();
12112         break;
12113       case EditGame:
12114       default:
12115         break;
12116     }
12117
12118 //    forwardMostMove = currentMove;
12119     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12120     ResurrectChessProgram();    /* in case first program isn't running */
12121
12122     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12123     DisplayMessage("", "");
12124     InitChessProgram(&second, FALSE);
12125     SendToProgram("force\n", &second);
12126     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12127       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12128       return;
12129     }
12130     if (startedFromSetupPosition) {
12131         SendBoard(&second, backwardMostMove);
12132     if (appData.debugMode) {
12133         fprintf(debugFP, "Two Machines\n");
12134     }
12135     }
12136     for (i = backwardMostMove; i < forwardMostMove; i++) {
12137         SendMoveToProgram(i, &second);
12138     }
12139
12140     gameMode = TwoMachinesPlay;
12141     pausing = FALSE;
12142     ModeHighlight();
12143     SetGameInfo();
12144     DisplayTwoMachinesTitle();
12145     firstMove = TRUE;
12146     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12147         onmove = &first;
12148     } else {
12149         onmove = &second;
12150     }
12151
12152     SendToProgram(first.computerString, &first);
12153     if (first.sendName) {
12154       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12155       SendToProgram(buf, &first);
12156     }
12157     SendToProgram(second.computerString, &second);
12158     if (second.sendName) {
12159       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12160       SendToProgram(buf, &second);
12161     }
12162
12163     ResetClocks();
12164     if (!first.sendTime || !second.sendTime) {
12165         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12166         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12167     }
12168     if (onmove->sendTime) {
12169       if (onmove->useColors) {
12170         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12171       }
12172       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12173     }
12174     if (onmove->useColors) {
12175       SendToProgram(onmove->twoMachinesColor, onmove);
12176     }
12177     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12178 //    SendToProgram("go\n", onmove);
12179     onmove->maybeThinking = TRUE;
12180     SetMachineThinkingEnables();
12181
12182     StartClocks();
12183
12184     if(bookHit) { // [HGM] book: simulate book reply
12185         static char bookMove[MSG_SIZ]; // a bit generous?
12186
12187         programStats.nodes = programStats.depth = programStats.time =
12188         programStats.score = programStats.got_only_move = 0;
12189         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12190
12191         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12192         strcat(bookMove, bookHit);
12193         savedMessage = bookMove; // args for deferred call
12194         savedState = onmove;
12195         ScheduleDelayedEvent(DeferredBookMove, 1);
12196     }
12197 }
12198
12199 void
12200 TrainingEvent()
12201 {
12202     if (gameMode == Training) {
12203       SetTrainingModeOff();
12204       gameMode = PlayFromGameFile;
12205       DisplayMessage("", _("Training mode off"));
12206     } else {
12207       gameMode = Training;
12208       animateTraining = appData.animate;
12209
12210       /* make sure we are not already at the end of the game */
12211       if (currentMove < forwardMostMove) {
12212         SetTrainingModeOn();
12213         DisplayMessage("", _("Training mode on"));
12214       } else {
12215         gameMode = PlayFromGameFile;
12216         DisplayError(_("Already at end of game"), 0);
12217       }
12218     }
12219     ModeHighlight();
12220 }
12221
12222 void
12223 IcsClientEvent()
12224 {
12225     if (!appData.icsActive) return;
12226     switch (gameMode) {
12227       case IcsPlayingWhite:
12228       case IcsPlayingBlack:
12229       case IcsObserving:
12230       case IcsIdle:
12231       case BeginningOfGame:
12232       case IcsExamining:
12233         return;
12234
12235       case EditGame:
12236         break;
12237
12238       case EditPosition:
12239         EditPositionDone(TRUE);
12240         break;
12241
12242       case AnalyzeMode:
12243       case AnalyzeFile:
12244         ExitAnalyzeMode();
12245         break;
12246
12247       default:
12248         EditGameEvent();
12249         break;
12250     }
12251
12252     gameMode = IcsIdle;
12253     ModeHighlight();
12254     return;
12255 }
12256
12257
12258 void
12259 EditGameEvent()
12260 {
12261     int i;
12262
12263     switch (gameMode) {
12264       case Training:
12265         SetTrainingModeOff();
12266         break;
12267       case MachinePlaysWhite:
12268       case MachinePlaysBlack:
12269       case BeginningOfGame:
12270         SendToProgram("force\n", &first);
12271         SetUserThinkingEnables();
12272         break;
12273       case PlayFromGameFile:
12274         (void) StopLoadGameTimer();
12275         if (gameFileFP != NULL) {
12276             gameFileFP = NULL;
12277         }
12278         break;
12279       case EditPosition:
12280         EditPositionDone(TRUE);
12281         break;
12282       case AnalyzeMode:
12283       case AnalyzeFile:
12284         ExitAnalyzeMode();
12285         SendToProgram("force\n", &first);
12286         break;
12287       case TwoMachinesPlay:
12288         GameEnds(EndOfFile, NULL, GE_PLAYER);
12289         ResurrectChessProgram();
12290         SetUserThinkingEnables();
12291         break;
12292       case EndOfGame:
12293         ResurrectChessProgram();
12294         break;
12295       case IcsPlayingBlack:
12296       case IcsPlayingWhite:
12297         DisplayError(_("Warning: You are still playing a game"), 0);
12298         break;
12299       case IcsObserving:
12300         DisplayError(_("Warning: You are still observing a game"), 0);
12301         break;
12302       case IcsExamining:
12303         DisplayError(_("Warning: You are still examining a game"), 0);
12304         break;
12305       case IcsIdle:
12306         break;
12307       case EditGame:
12308       default:
12309         return;
12310     }
12311
12312     pausing = FALSE;
12313     StopClocks();
12314     first.offeredDraw = second.offeredDraw = 0;
12315
12316     if (gameMode == PlayFromGameFile) {
12317         whiteTimeRemaining = timeRemaining[0][currentMove];
12318         blackTimeRemaining = timeRemaining[1][currentMove];
12319         DisplayTitle("");
12320     }
12321
12322     if (gameMode == MachinePlaysWhite ||
12323         gameMode == MachinePlaysBlack ||
12324         gameMode == TwoMachinesPlay ||
12325         gameMode == EndOfGame) {
12326         i = forwardMostMove;
12327         while (i > currentMove) {
12328             SendToProgram("undo\n", &first);
12329             i--;
12330         }
12331         whiteTimeRemaining = timeRemaining[0][currentMove];
12332         blackTimeRemaining = timeRemaining[1][currentMove];
12333         DisplayBothClocks();
12334         if (whiteFlag || blackFlag) {
12335             whiteFlag = blackFlag = 0;
12336         }
12337         DisplayTitle("");
12338     }
12339
12340     gameMode = EditGame;
12341     ModeHighlight();
12342     SetGameInfo();
12343 }
12344
12345
12346 void
12347 EditPositionEvent()
12348 {
12349     if (gameMode == EditPosition) {
12350         EditGameEvent();
12351         return;
12352     }
12353
12354     EditGameEvent();
12355     if (gameMode != EditGame) return;
12356
12357     gameMode = EditPosition;
12358     ModeHighlight();
12359     SetGameInfo();
12360     if (currentMove > 0)
12361       CopyBoard(boards[0], boards[currentMove]);
12362
12363     blackPlaysFirst = !WhiteOnMove(currentMove);
12364     ResetClocks();
12365     currentMove = forwardMostMove = backwardMostMove = 0;
12366     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12367     DisplayMove(-1);
12368 }
12369
12370 void
12371 ExitAnalyzeMode()
12372 {
12373     /* [DM] icsEngineAnalyze - possible call from other functions */
12374     if (appData.icsEngineAnalyze) {
12375         appData.icsEngineAnalyze = FALSE;
12376
12377         DisplayMessage("",_("Close ICS engine analyze..."));
12378     }
12379     if (first.analysisSupport && first.analyzing) {
12380       SendToProgram("exit\n", &first);
12381       first.analyzing = FALSE;
12382     }
12383     thinkOutput[0] = NULLCHAR;
12384 }
12385
12386 void
12387 EditPositionDone(Boolean fakeRights)
12388 {
12389     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12390
12391     startedFromSetupPosition = TRUE;
12392     InitChessProgram(&first, FALSE);
12393     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12394       boards[0][EP_STATUS] = EP_NONE;
12395       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12396     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12397         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12398         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12399       } else boards[0][CASTLING][2] = NoRights;
12400     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12401         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12402         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12403       } else boards[0][CASTLING][5] = NoRights;
12404     }
12405     SendToProgram("force\n", &first);
12406     if (blackPlaysFirst) {
12407         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12408         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12409         currentMove = forwardMostMove = backwardMostMove = 1;
12410         CopyBoard(boards[1], boards[0]);
12411     } else {
12412         currentMove = forwardMostMove = backwardMostMove = 0;
12413     }
12414     SendBoard(&first, forwardMostMove);
12415     if (appData.debugMode) {
12416         fprintf(debugFP, "EditPosDone\n");
12417     }
12418     DisplayTitle("");
12419     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12420     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12421     gameMode = EditGame;
12422     ModeHighlight();
12423     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12424     ClearHighlights(); /* [AS] */
12425 }
12426
12427 /* Pause for `ms' milliseconds */
12428 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12429 void
12430 TimeDelay(ms)
12431      long ms;
12432 {
12433     TimeMark m1, m2;
12434
12435     GetTimeMark(&m1);
12436     do {
12437         GetTimeMark(&m2);
12438     } while (SubtractTimeMarks(&m2, &m1) < ms);
12439 }
12440
12441 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12442 void
12443 SendMultiLineToICS(buf)
12444      char *buf;
12445 {
12446     char temp[MSG_SIZ+1], *p;
12447     int len;
12448
12449     len = strlen(buf);
12450     if (len > MSG_SIZ)
12451       len = MSG_SIZ;
12452
12453     strncpy(temp, buf, len);
12454     temp[len] = 0;
12455
12456     p = temp;
12457     while (*p) {
12458         if (*p == '\n' || *p == '\r')
12459           *p = ' ';
12460         ++p;
12461     }
12462
12463     strcat(temp, "\n");
12464     SendToICS(temp);
12465     SendToPlayer(temp, strlen(temp));
12466 }
12467
12468 void
12469 SetWhiteToPlayEvent()
12470 {
12471     if (gameMode == EditPosition) {
12472         blackPlaysFirst = FALSE;
12473         DisplayBothClocks();    /* works because currentMove is 0 */
12474     } else if (gameMode == IcsExamining) {
12475         SendToICS(ics_prefix);
12476         SendToICS("tomove white\n");
12477     }
12478 }
12479
12480 void
12481 SetBlackToPlayEvent()
12482 {
12483     if (gameMode == EditPosition) {
12484         blackPlaysFirst = TRUE;
12485         currentMove = 1;        /* kludge */
12486         DisplayBothClocks();
12487         currentMove = 0;
12488     } else if (gameMode == IcsExamining) {
12489         SendToICS(ics_prefix);
12490         SendToICS("tomove black\n");
12491     }
12492 }
12493
12494 void
12495 EditPositionMenuEvent(selection, x, y)
12496      ChessSquare selection;
12497      int x, y;
12498 {
12499     char buf[MSG_SIZ];
12500     ChessSquare piece = boards[0][y][x];
12501
12502     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12503
12504     switch (selection) {
12505       case ClearBoard:
12506         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12507             SendToICS(ics_prefix);
12508             SendToICS("bsetup clear\n");
12509         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12510             SendToICS(ics_prefix);
12511             SendToICS("clearboard\n");
12512         } else {
12513             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12514                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12515                 for (y = 0; y < BOARD_HEIGHT; y++) {
12516                     if (gameMode == IcsExamining) {
12517                         if (boards[currentMove][y][x] != EmptySquare) {
12518                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12519                                     AAA + x, ONE + y);
12520                             SendToICS(buf);
12521                         }
12522                     } else {
12523                         boards[0][y][x] = p;
12524                     }
12525                 }
12526             }
12527         }
12528         if (gameMode == EditPosition) {
12529             DrawPosition(FALSE, boards[0]);
12530         }
12531         break;
12532
12533       case WhitePlay:
12534         SetWhiteToPlayEvent();
12535         break;
12536
12537       case BlackPlay:
12538         SetBlackToPlayEvent();
12539         break;
12540
12541       case EmptySquare:
12542         if (gameMode == IcsExamining) {
12543             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12544             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12545             SendToICS(buf);
12546         } else {
12547             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12548                 if(x == BOARD_LEFT-2) {
12549                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12550                     boards[0][y][1] = 0;
12551                 } else
12552                 if(x == BOARD_RGHT+1) {
12553                     if(y >= gameInfo.holdingsSize) break;
12554                     boards[0][y][BOARD_WIDTH-2] = 0;
12555                 } else break;
12556             }
12557             boards[0][y][x] = EmptySquare;
12558             DrawPosition(FALSE, boards[0]);
12559         }
12560         break;
12561
12562       case PromotePiece:
12563         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12564            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12565             selection = (ChessSquare) (PROMOTED piece);
12566         } else if(piece == EmptySquare) selection = WhiteSilver;
12567         else selection = (ChessSquare)((int)piece - 1);
12568         goto defaultlabel;
12569
12570       case DemotePiece:
12571         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12572            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12573             selection = (ChessSquare) (DEMOTED piece);
12574         } else if(piece == EmptySquare) selection = BlackSilver;
12575         else selection = (ChessSquare)((int)piece + 1);
12576         goto defaultlabel;
12577
12578       case WhiteQueen:
12579       case BlackQueen:
12580         if(gameInfo.variant == VariantShatranj ||
12581            gameInfo.variant == VariantXiangqi  ||
12582            gameInfo.variant == VariantCourier  ||
12583            gameInfo.variant == VariantMakruk     )
12584             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12585         goto defaultlabel;
12586
12587       case WhiteKing:
12588       case BlackKing:
12589         if(gameInfo.variant == VariantXiangqi)
12590             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12591         if(gameInfo.variant == VariantKnightmate)
12592             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12593       default:
12594         defaultlabel:
12595         if (gameMode == IcsExamining) {
12596             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12597             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12598                      PieceToChar(selection), AAA + x, ONE + y);
12599             SendToICS(buf);
12600         } else {
12601             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12602                 int n;
12603                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12604                     n = PieceToNumber(selection - BlackPawn);
12605                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12606                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12607                     boards[0][BOARD_HEIGHT-1-n][1]++;
12608                 } else
12609                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12610                     n = PieceToNumber(selection);
12611                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12612                     boards[0][n][BOARD_WIDTH-1] = selection;
12613                     boards[0][n][BOARD_WIDTH-2]++;
12614                 }
12615             } else
12616             boards[0][y][x] = selection;
12617             DrawPosition(TRUE, boards[0]);
12618         }
12619         break;
12620     }
12621 }
12622
12623
12624 void
12625 DropMenuEvent(selection, x, y)
12626      ChessSquare selection;
12627      int x, y;
12628 {
12629     ChessMove moveType;
12630
12631     switch (gameMode) {
12632       case IcsPlayingWhite:
12633       case MachinePlaysBlack:
12634         if (!WhiteOnMove(currentMove)) {
12635             DisplayMoveError(_("It is Black's turn"));
12636             return;
12637         }
12638         moveType = WhiteDrop;
12639         break;
12640       case IcsPlayingBlack:
12641       case MachinePlaysWhite:
12642         if (WhiteOnMove(currentMove)) {
12643             DisplayMoveError(_("It is White's turn"));
12644             return;
12645         }
12646         moveType = BlackDrop;
12647         break;
12648       case EditGame:
12649         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12650         break;
12651       default:
12652         return;
12653     }
12654
12655     if (moveType == BlackDrop && selection < BlackPawn) {
12656       selection = (ChessSquare) ((int) selection
12657                                  + (int) BlackPawn - (int) WhitePawn);
12658     }
12659     if (boards[currentMove][y][x] != EmptySquare) {
12660         DisplayMoveError(_("That square is occupied"));
12661         return;
12662     }
12663
12664     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12665 }
12666
12667 void
12668 AcceptEvent()
12669 {
12670     /* Accept a pending offer of any kind from opponent */
12671
12672     if (appData.icsActive) {
12673         SendToICS(ics_prefix);
12674         SendToICS("accept\n");
12675     } else if (cmailMsgLoaded) {
12676         if (currentMove == cmailOldMove &&
12677             commentList[cmailOldMove] != NULL &&
12678             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12679                    "Black offers a draw" : "White offers a draw")) {
12680             TruncateGame();
12681             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12682             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12683         } else {
12684             DisplayError(_("There is no pending offer on this move"), 0);
12685             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12686         }
12687     } else {
12688         /* Not used for offers from chess program */
12689     }
12690 }
12691
12692 void
12693 DeclineEvent()
12694 {
12695     /* Decline a pending offer of any kind from opponent */
12696
12697     if (appData.icsActive) {
12698         SendToICS(ics_prefix);
12699         SendToICS("decline\n");
12700     } else if (cmailMsgLoaded) {
12701         if (currentMove == cmailOldMove &&
12702             commentList[cmailOldMove] != NULL &&
12703             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12704                    "Black offers a draw" : "White offers a draw")) {
12705 #ifdef NOTDEF
12706             AppendComment(cmailOldMove, "Draw declined", TRUE);
12707             DisplayComment(cmailOldMove - 1, "Draw declined");
12708 #endif /*NOTDEF*/
12709         } else {
12710             DisplayError(_("There is no pending offer on this move"), 0);
12711         }
12712     } else {
12713         /* Not used for offers from chess program */
12714     }
12715 }
12716
12717 void
12718 RematchEvent()
12719 {
12720     /* Issue ICS rematch command */
12721     if (appData.icsActive) {
12722         SendToICS(ics_prefix);
12723         SendToICS("rematch\n");
12724     }
12725 }
12726
12727 void
12728 CallFlagEvent()
12729 {
12730     /* Call your opponent's flag (claim a win on time) */
12731     if (appData.icsActive) {
12732         SendToICS(ics_prefix);
12733         SendToICS("flag\n");
12734     } else {
12735         switch (gameMode) {
12736           default:
12737             return;
12738           case MachinePlaysWhite:
12739             if (whiteFlag) {
12740                 if (blackFlag)
12741                   GameEnds(GameIsDrawn, "Both players ran out of time",
12742                            GE_PLAYER);
12743                 else
12744                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12745             } else {
12746                 DisplayError(_("Your opponent is not out of time"), 0);
12747             }
12748             break;
12749           case MachinePlaysBlack:
12750             if (blackFlag) {
12751                 if (whiteFlag)
12752                   GameEnds(GameIsDrawn, "Both players ran out of time",
12753                            GE_PLAYER);
12754                 else
12755                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12756             } else {
12757                 DisplayError(_("Your opponent is not out of time"), 0);
12758             }
12759             break;
12760         }
12761     }
12762 }
12763
12764 void
12765 ClockClick(int which)
12766 {       // [HGM] code moved to back-end from winboard.c
12767         if(which) { // black clock
12768           if (gameMode == EditPosition || gameMode == IcsExamining) {
12769             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12770             SetBlackToPlayEvent();
12771           } else if (gameMode == EditGame || shiftKey) {
12772             AdjustClock(which, -1);
12773           } else if (gameMode == IcsPlayingWhite ||
12774                      gameMode == MachinePlaysBlack) {
12775             CallFlagEvent();
12776           }
12777         } else { // white clock
12778           if (gameMode == EditPosition || gameMode == IcsExamining) {
12779             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12780             SetWhiteToPlayEvent();
12781           } else if (gameMode == EditGame || shiftKey) {
12782             AdjustClock(which, -1);
12783           } else if (gameMode == IcsPlayingBlack ||
12784                    gameMode == MachinePlaysWhite) {
12785             CallFlagEvent();
12786           }
12787         }
12788 }
12789
12790 void
12791 DrawEvent()
12792 {
12793     /* Offer draw or accept pending draw offer from opponent */
12794
12795     if (appData.icsActive) {
12796         /* Note: tournament rules require draw offers to be
12797            made after you make your move but before you punch
12798            your clock.  Currently ICS doesn't let you do that;
12799            instead, you immediately punch your clock after making
12800            a move, but you can offer a draw at any time. */
12801
12802         SendToICS(ics_prefix);
12803         SendToICS("draw\n");
12804         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12805     } else if (cmailMsgLoaded) {
12806         if (currentMove == cmailOldMove &&
12807             commentList[cmailOldMove] != NULL &&
12808             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12809                    "Black offers a draw" : "White offers a draw")) {
12810             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12811             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12812         } else if (currentMove == cmailOldMove + 1) {
12813             char *offer = WhiteOnMove(cmailOldMove) ?
12814               "White offers a draw" : "Black offers a draw";
12815             AppendComment(currentMove, offer, TRUE);
12816             DisplayComment(currentMove - 1, offer);
12817             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12818         } else {
12819             DisplayError(_("You must make your move before offering a draw"), 0);
12820             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12821         }
12822     } else if (first.offeredDraw) {
12823         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12824     } else {
12825         if (first.sendDrawOffers) {
12826             SendToProgram("draw\n", &first);
12827             userOfferedDraw = TRUE;
12828         }
12829     }
12830 }
12831
12832 void
12833 AdjournEvent()
12834 {
12835     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12836
12837     if (appData.icsActive) {
12838         SendToICS(ics_prefix);
12839         SendToICS("adjourn\n");
12840     } else {
12841         /* Currently GNU Chess doesn't offer or accept Adjourns */
12842     }
12843 }
12844
12845
12846 void
12847 AbortEvent()
12848 {
12849     /* Offer Abort or accept pending Abort offer from opponent */
12850
12851     if (appData.icsActive) {
12852         SendToICS(ics_prefix);
12853         SendToICS("abort\n");
12854     } else {
12855         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12856     }
12857 }
12858
12859 void
12860 ResignEvent()
12861 {
12862     /* Resign.  You can do this even if it's not your turn. */
12863
12864     if (appData.icsActive) {
12865         SendToICS(ics_prefix);
12866         SendToICS("resign\n");
12867     } else {
12868         switch (gameMode) {
12869           case MachinePlaysWhite:
12870             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12871             break;
12872           case MachinePlaysBlack:
12873             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12874             break;
12875           case EditGame:
12876             if (cmailMsgLoaded) {
12877                 TruncateGame();
12878                 if (WhiteOnMove(cmailOldMove)) {
12879                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12880                 } else {
12881                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12882                 }
12883                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12884             }
12885             break;
12886           default:
12887             break;
12888         }
12889     }
12890 }
12891
12892
12893 void
12894 StopObservingEvent()
12895 {
12896     /* Stop observing current games */
12897     SendToICS(ics_prefix);
12898     SendToICS("unobserve\n");
12899 }
12900
12901 void
12902 StopExaminingEvent()
12903 {
12904     /* Stop observing current game */
12905     SendToICS(ics_prefix);
12906     SendToICS("unexamine\n");
12907 }
12908
12909 void
12910 ForwardInner(target)
12911      int target;
12912 {
12913     int limit;
12914
12915     if (appData.debugMode)
12916         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12917                 target, currentMove, forwardMostMove);
12918
12919     if (gameMode == EditPosition)
12920       return;
12921
12922     if (gameMode == PlayFromGameFile && !pausing)
12923       PauseEvent();
12924
12925     if (gameMode == IcsExamining && pausing)
12926       limit = pauseExamForwardMostMove;
12927     else
12928       limit = forwardMostMove;
12929
12930     if (target > limit) target = limit;
12931
12932     if (target > 0 && moveList[target - 1][0]) {
12933         int fromX, fromY, toX, toY;
12934         toX = moveList[target - 1][2] - AAA;
12935         toY = moveList[target - 1][3] - ONE;
12936         if (moveList[target - 1][1] == '@') {
12937             if (appData.highlightLastMove) {
12938                 SetHighlights(-1, -1, toX, toY);
12939             }
12940         } else {
12941             fromX = moveList[target - 1][0] - AAA;
12942             fromY = moveList[target - 1][1] - ONE;
12943             if (target == currentMove + 1) {
12944                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12945             }
12946             if (appData.highlightLastMove) {
12947                 SetHighlights(fromX, fromY, toX, toY);
12948             }
12949         }
12950     }
12951     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12952         gameMode == Training || gameMode == PlayFromGameFile ||
12953         gameMode == AnalyzeFile) {
12954         while (currentMove < target) {
12955             SendMoveToProgram(currentMove++, &first);
12956         }
12957     } else {
12958         currentMove = target;
12959     }
12960
12961     if (gameMode == EditGame || gameMode == EndOfGame) {
12962         whiteTimeRemaining = timeRemaining[0][currentMove];
12963         blackTimeRemaining = timeRemaining[1][currentMove];
12964     }
12965     DisplayBothClocks();
12966     DisplayMove(currentMove - 1);
12967     DrawPosition(FALSE, boards[currentMove]);
12968     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12969     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12970         DisplayComment(currentMove - 1, commentList[currentMove]);
12971     }
12972 }
12973
12974
12975 void
12976 ForwardEvent()
12977 {
12978     if (gameMode == IcsExamining && !pausing) {
12979         SendToICS(ics_prefix);
12980         SendToICS("forward\n");
12981     } else {
12982         ForwardInner(currentMove + 1);
12983     }
12984 }
12985
12986 void
12987 ToEndEvent()
12988 {
12989     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12990         /* to optimze, we temporarily turn off analysis mode while we feed
12991          * the remaining moves to the engine. Otherwise we get analysis output
12992          * after each move.
12993          */
12994         if (first.analysisSupport) {
12995           SendToProgram("exit\nforce\n", &first);
12996           first.analyzing = FALSE;
12997         }
12998     }
12999
13000     if (gameMode == IcsExamining && !pausing) {
13001         SendToICS(ics_prefix);
13002         SendToICS("forward 999999\n");
13003     } else {
13004         ForwardInner(forwardMostMove);
13005     }
13006
13007     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13008         /* we have fed all the moves, so reactivate analysis mode */
13009         SendToProgram("analyze\n", &first);
13010         first.analyzing = TRUE;
13011         /*first.maybeThinking = TRUE;*/
13012         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13013     }
13014 }
13015
13016 void
13017 BackwardInner(target)
13018      int target;
13019 {
13020     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13021
13022     if (appData.debugMode)
13023         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13024                 target, currentMove, forwardMostMove);
13025
13026     if (gameMode == EditPosition) return;
13027     if (currentMove <= backwardMostMove) {
13028         ClearHighlights();
13029         DrawPosition(full_redraw, boards[currentMove]);
13030         return;
13031     }
13032     if (gameMode == PlayFromGameFile && !pausing)
13033       PauseEvent();
13034
13035     if (moveList[target][0]) {
13036         int fromX, fromY, toX, toY;
13037         toX = moveList[target][2] - AAA;
13038         toY = moveList[target][3] - ONE;
13039         if (moveList[target][1] == '@') {
13040             if (appData.highlightLastMove) {
13041                 SetHighlights(-1, -1, toX, toY);
13042             }
13043         } else {
13044             fromX = moveList[target][0] - AAA;
13045             fromY = moveList[target][1] - ONE;
13046             if (target == currentMove - 1) {
13047                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13048             }
13049             if (appData.highlightLastMove) {
13050                 SetHighlights(fromX, fromY, toX, toY);
13051             }
13052         }
13053     }
13054     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13055         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13056         while (currentMove > target) {
13057             SendToProgram("undo\n", &first);
13058             currentMove--;
13059         }
13060     } else {
13061         currentMove = target;
13062     }
13063
13064     if (gameMode == EditGame || gameMode == EndOfGame) {
13065         whiteTimeRemaining = timeRemaining[0][currentMove];
13066         blackTimeRemaining = timeRemaining[1][currentMove];
13067     }
13068     DisplayBothClocks();
13069     DisplayMove(currentMove - 1);
13070     DrawPosition(full_redraw, boards[currentMove]);
13071     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13072     // [HGM] PV info: routine tests if comment empty
13073     DisplayComment(currentMove - 1, commentList[currentMove]);
13074 }
13075
13076 void
13077 BackwardEvent()
13078 {
13079     if (gameMode == IcsExamining && !pausing) {
13080         SendToICS(ics_prefix);
13081         SendToICS("backward\n");
13082     } else {
13083         BackwardInner(currentMove - 1);
13084     }
13085 }
13086
13087 void
13088 ToStartEvent()
13089 {
13090     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13091         /* to optimize, we temporarily turn off analysis mode while we undo
13092          * all the moves. Otherwise we get analysis output after each undo.
13093          */
13094         if (first.analysisSupport) {
13095           SendToProgram("exit\nforce\n", &first);
13096           first.analyzing = FALSE;
13097         }
13098     }
13099
13100     if (gameMode == IcsExamining && !pausing) {
13101         SendToICS(ics_prefix);
13102         SendToICS("backward 999999\n");
13103     } else {
13104         BackwardInner(backwardMostMove);
13105     }
13106
13107     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13108         /* we have fed all the moves, so reactivate analysis mode */
13109         SendToProgram("analyze\n", &first);
13110         first.analyzing = TRUE;
13111         /*first.maybeThinking = TRUE;*/
13112         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13113     }
13114 }
13115
13116 void
13117 ToNrEvent(int to)
13118 {
13119   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13120   if (to >= forwardMostMove) to = forwardMostMove;
13121   if (to <= backwardMostMove) to = backwardMostMove;
13122   if (to < currentMove) {
13123     BackwardInner(to);
13124   } else {
13125     ForwardInner(to);
13126   }
13127 }
13128
13129 void
13130 RevertEvent(Boolean annotate)
13131 {
13132     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13133         return;
13134     }
13135     if (gameMode != IcsExamining) {
13136         DisplayError(_("You are not examining a game"), 0);
13137         return;
13138     }
13139     if (pausing) {
13140         DisplayError(_("You can't revert while pausing"), 0);
13141         return;
13142     }
13143     SendToICS(ics_prefix);
13144     SendToICS("revert\n");
13145 }
13146
13147 void
13148 RetractMoveEvent()
13149 {
13150     switch (gameMode) {
13151       case MachinePlaysWhite:
13152       case MachinePlaysBlack:
13153         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13154             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13155             return;
13156         }
13157         if (forwardMostMove < 2) return;
13158         currentMove = forwardMostMove = forwardMostMove - 2;
13159         whiteTimeRemaining = timeRemaining[0][currentMove];
13160         blackTimeRemaining = timeRemaining[1][currentMove];
13161         DisplayBothClocks();
13162         DisplayMove(currentMove - 1);
13163         ClearHighlights();/*!! could figure this out*/
13164         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13165         SendToProgram("remove\n", &first);
13166         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13167         break;
13168
13169       case BeginningOfGame:
13170       default:
13171         break;
13172
13173       case IcsPlayingWhite:
13174       case IcsPlayingBlack:
13175         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13176             SendToICS(ics_prefix);
13177             SendToICS("takeback 2\n");
13178         } else {
13179             SendToICS(ics_prefix);
13180             SendToICS("takeback 1\n");
13181         }
13182         break;
13183     }
13184 }
13185
13186 void
13187 MoveNowEvent()
13188 {
13189     ChessProgramState *cps;
13190
13191     switch (gameMode) {
13192       case MachinePlaysWhite:
13193         if (!WhiteOnMove(forwardMostMove)) {
13194             DisplayError(_("It is your turn"), 0);
13195             return;
13196         }
13197         cps = &first;
13198         break;
13199       case MachinePlaysBlack:
13200         if (WhiteOnMove(forwardMostMove)) {
13201             DisplayError(_("It is your turn"), 0);
13202             return;
13203         }
13204         cps = &first;
13205         break;
13206       case TwoMachinesPlay:
13207         if (WhiteOnMove(forwardMostMove) ==
13208             (first.twoMachinesColor[0] == 'w')) {
13209             cps = &first;
13210         } else {
13211             cps = &second;
13212         }
13213         break;
13214       case BeginningOfGame:
13215       default:
13216         return;
13217     }
13218     SendToProgram("?\n", cps);
13219 }
13220
13221 void
13222 TruncateGameEvent()
13223 {
13224     EditGameEvent();
13225     if (gameMode != EditGame) return;
13226     TruncateGame();
13227 }
13228
13229 void
13230 TruncateGame()
13231 {
13232     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13233     if (forwardMostMove > currentMove) {
13234         if (gameInfo.resultDetails != NULL) {
13235             free(gameInfo.resultDetails);
13236             gameInfo.resultDetails = NULL;
13237             gameInfo.result = GameUnfinished;
13238         }
13239         forwardMostMove = currentMove;
13240         HistorySet(parseList, backwardMostMove, forwardMostMove,
13241                    currentMove-1);
13242     }
13243 }
13244
13245 void
13246 HintEvent()
13247 {
13248     if (appData.noChessProgram) return;
13249     switch (gameMode) {
13250       case MachinePlaysWhite:
13251         if (WhiteOnMove(forwardMostMove)) {
13252             DisplayError(_("Wait until your turn"), 0);
13253             return;
13254         }
13255         break;
13256       case BeginningOfGame:
13257       case MachinePlaysBlack:
13258         if (!WhiteOnMove(forwardMostMove)) {
13259             DisplayError(_("Wait until your turn"), 0);
13260             return;
13261         }
13262         break;
13263       default:
13264         DisplayError(_("No hint available"), 0);
13265         return;
13266     }
13267     SendToProgram("hint\n", &first);
13268     hintRequested = TRUE;
13269 }
13270
13271 void
13272 BookEvent()
13273 {
13274     if (appData.noChessProgram) return;
13275     switch (gameMode) {
13276       case MachinePlaysWhite:
13277         if (WhiteOnMove(forwardMostMove)) {
13278             DisplayError(_("Wait until your turn"), 0);
13279             return;
13280         }
13281         break;
13282       case BeginningOfGame:
13283       case MachinePlaysBlack:
13284         if (!WhiteOnMove(forwardMostMove)) {
13285             DisplayError(_("Wait until your turn"), 0);
13286             return;
13287         }
13288         break;
13289       case EditPosition:
13290         EditPositionDone(TRUE);
13291         break;
13292       case TwoMachinesPlay:
13293         return;
13294       default:
13295         break;
13296     }
13297     SendToProgram("bk\n", &first);
13298     bookOutput[0] = NULLCHAR;
13299     bookRequested = TRUE;
13300 }
13301
13302 void
13303 AboutGameEvent()
13304 {
13305     char *tags = PGNTags(&gameInfo);
13306     TagsPopUp(tags, CmailMsg());
13307     free(tags);
13308 }
13309
13310 /* end button procedures */
13311
13312 void
13313 PrintPosition(fp, move)
13314      FILE *fp;
13315      int move;
13316 {
13317     int i, j;
13318
13319     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13320         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13321             char c = PieceToChar(boards[move][i][j]);
13322             fputc(c == 'x' ? '.' : c, fp);
13323             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13324         }
13325     }
13326     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13327       fprintf(fp, "white to play\n");
13328     else
13329       fprintf(fp, "black to play\n");
13330 }
13331
13332 void
13333 PrintOpponents(fp)
13334      FILE *fp;
13335 {
13336     if (gameInfo.white != NULL) {
13337         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13338     } else {
13339         fprintf(fp, "\n");
13340     }
13341 }
13342
13343 /* Find last component of program's own name, using some heuristics */
13344 void
13345 TidyProgramName(prog, host, buf)
13346      char *prog, *host, buf[MSG_SIZ];
13347 {
13348     char *p, *q;
13349     int local = (strcmp(host, "localhost") == 0);
13350     while (!local && (p = strchr(prog, ';')) != NULL) {
13351         p++;
13352         while (*p == ' ') p++;
13353         prog = p;
13354     }
13355     if (*prog == '"' || *prog == '\'') {
13356         q = strchr(prog + 1, *prog);
13357     } else {
13358         q = strchr(prog, ' ');
13359     }
13360     if (q == NULL) q = prog + strlen(prog);
13361     p = q;
13362     while (p >= prog && *p != '/' && *p != '\\') p--;
13363     p++;
13364     if(p == prog && *p == '"') p++;
13365     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13366     memcpy(buf, p, q - p);
13367     buf[q - p] = NULLCHAR;
13368     if (!local) {
13369         strcat(buf, "@");
13370         strcat(buf, host);
13371     }
13372 }
13373
13374 char *
13375 TimeControlTagValue()
13376 {
13377     char buf[MSG_SIZ];
13378     if (!appData.clockMode) {
13379       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13380     } else if (movesPerSession > 0) {
13381       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13382     } else if (timeIncrement == 0) {
13383       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13384     } else {
13385       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13386     }
13387     return StrSave(buf);
13388 }
13389
13390 void
13391 SetGameInfo()
13392 {
13393     /* This routine is used only for certain modes */
13394     VariantClass v = gameInfo.variant;
13395     ChessMove r = GameUnfinished;
13396     char *p = NULL;
13397
13398     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13399         r = gameInfo.result;
13400         p = gameInfo.resultDetails;
13401         gameInfo.resultDetails = NULL;
13402     }
13403     ClearGameInfo(&gameInfo);
13404     gameInfo.variant = v;
13405
13406     switch (gameMode) {
13407       case MachinePlaysWhite:
13408         gameInfo.event = StrSave( appData.pgnEventHeader );
13409         gameInfo.site = StrSave(HostName());
13410         gameInfo.date = PGNDate();
13411         gameInfo.round = StrSave("-");
13412         gameInfo.white = StrSave(first.tidy);
13413         gameInfo.black = StrSave(UserName());
13414         gameInfo.timeControl = TimeControlTagValue();
13415         break;
13416
13417       case MachinePlaysBlack:
13418         gameInfo.event = StrSave( appData.pgnEventHeader );
13419         gameInfo.site = StrSave(HostName());
13420         gameInfo.date = PGNDate();
13421         gameInfo.round = StrSave("-");
13422         gameInfo.white = StrSave(UserName());
13423         gameInfo.black = StrSave(first.tidy);
13424         gameInfo.timeControl = TimeControlTagValue();
13425         break;
13426
13427       case TwoMachinesPlay:
13428         gameInfo.event = StrSave( appData.pgnEventHeader );
13429         gameInfo.site = StrSave(HostName());
13430         gameInfo.date = PGNDate();
13431         if (matchGame > 0) {
13432             char buf[MSG_SIZ];
13433             snprintf(buf, MSG_SIZ, "%d", matchGame);
13434             gameInfo.round = StrSave(buf);
13435         } else {
13436             gameInfo.round = StrSave("-");
13437         }
13438         if (first.twoMachinesColor[0] == 'w') {
13439             gameInfo.white = StrSave(first.tidy);
13440             gameInfo.black = StrSave(second.tidy);
13441         } else {
13442             gameInfo.white = StrSave(second.tidy);
13443             gameInfo.black = StrSave(first.tidy);
13444         }
13445         gameInfo.timeControl = TimeControlTagValue();
13446         break;
13447
13448       case EditGame:
13449         gameInfo.event = StrSave("Edited game");
13450         gameInfo.site = StrSave(HostName());
13451         gameInfo.date = PGNDate();
13452         gameInfo.round = StrSave("-");
13453         gameInfo.white = StrSave("-");
13454         gameInfo.black = StrSave("-");
13455         gameInfo.result = r;
13456         gameInfo.resultDetails = p;
13457         break;
13458
13459       case EditPosition:
13460         gameInfo.event = StrSave("Edited position");
13461         gameInfo.site = StrSave(HostName());
13462         gameInfo.date = PGNDate();
13463         gameInfo.round = StrSave("-");
13464         gameInfo.white = StrSave("-");
13465         gameInfo.black = StrSave("-");
13466         break;
13467
13468       case IcsPlayingWhite:
13469       case IcsPlayingBlack:
13470       case IcsObserving:
13471       case IcsExamining:
13472         break;
13473
13474       case PlayFromGameFile:
13475         gameInfo.event = StrSave("Game from non-PGN file");
13476         gameInfo.site = StrSave(HostName());
13477         gameInfo.date = PGNDate();
13478         gameInfo.round = StrSave("-");
13479         gameInfo.white = StrSave("?");
13480         gameInfo.black = StrSave("?");
13481         break;
13482
13483       default:
13484         break;
13485     }
13486 }
13487
13488 void
13489 ReplaceComment(index, text)
13490      int index;
13491      char *text;
13492 {
13493     int len;
13494     char *p;
13495     float score;
13496
13497     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13498        pvInfoList[index-1].depth == len &&
13499        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13500        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13501     while (*text == '\n') text++;
13502     len = strlen(text);
13503     while (len > 0 && text[len - 1] == '\n') len--;
13504
13505     if (commentList[index] != NULL)
13506       free(commentList[index]);
13507
13508     if (len == 0) {
13509         commentList[index] = NULL;
13510         return;
13511     }
13512   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13513       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13514       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13515     commentList[index] = (char *) malloc(len + 2);
13516     strncpy(commentList[index], text, len);
13517     commentList[index][len] = '\n';
13518     commentList[index][len + 1] = NULLCHAR;
13519   } else {
13520     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13521     char *p;
13522     commentList[index] = (char *) malloc(len + 7);
13523     safeStrCpy(commentList[index], "{\n", 3);
13524     safeStrCpy(commentList[index]+2, text, len+1);
13525     commentList[index][len+2] = NULLCHAR;
13526     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13527     strcat(commentList[index], "\n}\n");
13528   }
13529 }
13530
13531 void
13532 CrushCRs(text)
13533      char *text;
13534 {
13535   char *p = text;
13536   char *q = text;
13537   char ch;
13538
13539   do {
13540     ch = *p++;
13541     if (ch == '\r') continue;
13542     *q++ = ch;
13543   } while (ch != '\0');
13544 }
13545
13546 void
13547 AppendComment(index, text, addBraces)
13548      int index;
13549      char *text;
13550      Boolean addBraces; // [HGM] braces: tells if we should add {}
13551 {
13552     int oldlen, len;
13553     char *old;
13554
13555 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13556     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13557
13558     CrushCRs(text);
13559     while (*text == '\n') text++;
13560     len = strlen(text);
13561     while (len > 0 && text[len - 1] == '\n') len--;
13562
13563     if (len == 0) return;
13564
13565     if (commentList[index] != NULL) {
13566         old = commentList[index];
13567         oldlen = strlen(old);
13568         while(commentList[index][oldlen-1] ==  '\n')
13569           commentList[index][--oldlen] = NULLCHAR;
13570         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13571         safeStrCpy(commentList[index], old, oldlen + len + 6);
13572         free(old);
13573         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13574         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13575           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13576           while (*text == '\n') { text++; len--; }
13577           commentList[index][--oldlen] = NULLCHAR;
13578       }
13579         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13580         else          strcat(commentList[index], "\n");
13581         strcat(commentList[index], text);
13582         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13583         else          strcat(commentList[index], "\n");
13584     } else {
13585         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13586         if(addBraces)
13587           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13588         else commentList[index][0] = NULLCHAR;
13589         strcat(commentList[index], text);
13590         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13591         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13592     }
13593 }
13594
13595 static char * FindStr( char * text, char * sub_text )
13596 {
13597     char * result = strstr( text, sub_text );
13598
13599     if( result != NULL ) {
13600         result += strlen( sub_text );
13601     }
13602
13603     return result;
13604 }
13605
13606 /* [AS] Try to extract PV info from PGN comment */
13607 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13608 char *GetInfoFromComment( int index, char * text )
13609 {
13610     char * sep = text, *p;
13611
13612     if( text != NULL && index > 0 ) {
13613         int score = 0;
13614         int depth = 0;
13615         int time = -1, sec = 0, deci;
13616         char * s_eval = FindStr( text, "[%eval " );
13617         char * s_emt = FindStr( text, "[%emt " );
13618
13619         if( s_eval != NULL || s_emt != NULL ) {
13620             /* New style */
13621             char delim;
13622
13623             if( s_eval != NULL ) {
13624                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13625                     return text;
13626                 }
13627
13628                 if( delim != ']' ) {
13629                     return text;
13630                 }
13631             }
13632
13633             if( s_emt != NULL ) {
13634             }
13635                 return text;
13636         }
13637         else {
13638             /* We expect something like: [+|-]nnn.nn/dd */
13639             int score_lo = 0;
13640
13641             if(*text != '{') return text; // [HGM] braces: must be normal comment
13642
13643             sep = strchr( text, '/' );
13644             if( sep == NULL || sep < (text+4) ) {
13645                 return text;
13646             }
13647
13648             p = text;
13649             if(p[1] == '(') { // comment starts with PV
13650                p = strchr(p, ')'); // locate end of PV
13651                if(p == NULL || sep < p+5) return text;
13652                // at this point we have something like "{(.*) +0.23/6 ..."
13653                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13654                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13655                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13656             }
13657             time = -1; sec = -1; deci = -1;
13658             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13659                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13660                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13661                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13662                 return text;
13663             }
13664
13665             if( score_lo < 0 || score_lo >= 100 ) {
13666                 return text;
13667             }
13668
13669             if(sec >= 0) time = 600*time + 10*sec; else
13670             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13671
13672             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13673
13674             /* [HGM] PV time: now locate end of PV info */
13675             while( *++sep >= '0' && *sep <= '9'); // strip depth
13676             if(time >= 0)
13677             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13678             if(sec >= 0)
13679             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13680             if(deci >= 0)
13681             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13682             while(*sep == ' ') sep++;
13683         }
13684
13685         if( depth <= 0 ) {
13686             return text;
13687         }
13688
13689         if( time < 0 ) {
13690             time = -1;
13691         }
13692
13693         pvInfoList[index-1].depth = depth;
13694         pvInfoList[index-1].score = score;
13695         pvInfoList[index-1].time  = 10*time; // centi-sec
13696         if(*sep == '}') *sep = 0; else *--sep = '{';
13697         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13698     }
13699     return sep;
13700 }
13701
13702 void
13703 SendToProgram(message, cps)
13704      char *message;
13705      ChessProgramState *cps;
13706 {
13707     int count, outCount, error;
13708     char buf[MSG_SIZ];
13709
13710     if (cps->pr == NULL) return;
13711     Attention(cps);
13712
13713     if (appData.debugMode) {
13714         TimeMark now;
13715         GetTimeMark(&now);
13716         fprintf(debugFP, "%ld >%-6s: %s",
13717                 SubtractTimeMarks(&now, &programStartTime),
13718                 cps->which, message);
13719     }
13720
13721     count = strlen(message);
13722     outCount = OutputToProcess(cps->pr, message, count, &error);
13723     if (outCount < count && !exiting
13724                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13725       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13726         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13727             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13728                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13729                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13730             } else {
13731                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13732             }
13733             gameInfo.resultDetails = StrSave(buf);
13734         }
13735         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13736     }
13737 }
13738
13739 void
13740 ReceiveFromProgram(isr, closure, message, count, error)
13741      InputSourceRef isr;
13742      VOIDSTAR closure;
13743      char *message;
13744      int count;
13745      int error;
13746 {
13747     char *end_str;
13748     char buf[MSG_SIZ];
13749     ChessProgramState *cps = (ChessProgramState *)closure;
13750
13751     if (isr != cps->isr) return; /* Killed intentionally */
13752     if (count <= 0) {
13753         if (count == 0) {
13754             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13755                     _(cps->which), cps->program);
13756         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13757                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13758                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13759                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13760                 } else {
13761                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13762                 }
13763                 gameInfo.resultDetails = StrSave(buf);
13764             }
13765             RemoveInputSource(cps->isr);
13766             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13767         } else {
13768             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13769                     _(cps->which), cps->program);
13770             RemoveInputSource(cps->isr);
13771
13772             /* [AS] Program is misbehaving badly... kill it */
13773             if( count == -2 ) {
13774                 DestroyChildProcess( cps->pr, 9 );
13775                 cps->pr = NoProc;
13776             }
13777
13778             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13779         }
13780         return;
13781     }
13782
13783     if ((end_str = strchr(message, '\r')) != NULL)
13784       *end_str = NULLCHAR;
13785     if ((end_str = strchr(message, '\n')) != NULL)
13786       *end_str = NULLCHAR;
13787
13788     if (appData.debugMode) {
13789         TimeMark now; int print = 1;
13790         char *quote = ""; char c; int i;
13791
13792         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13793                 char start = message[0];
13794                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13795                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13796                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13797                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13798                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13799                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13800                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13801                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13802                    sscanf(message, "hint: %c", &c)!=1 && 
13803                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13804                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13805                     print = (appData.engineComments >= 2);
13806                 }
13807                 message[0] = start; // restore original message
13808         }
13809         if(print) {
13810                 GetTimeMark(&now);
13811                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13812                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13813                         quote,
13814                         message);
13815         }
13816     }
13817
13818     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13819     if (appData.icsEngineAnalyze) {
13820         if (strstr(message, "whisper") != NULL ||
13821              strstr(message, "kibitz") != NULL ||
13822             strstr(message, "tellics") != NULL) return;
13823     }
13824
13825     HandleMachineMove(message, cps);
13826 }
13827
13828
13829 void
13830 SendTimeControl(cps, mps, tc, inc, sd, st)
13831      ChessProgramState *cps;
13832      int mps, inc, sd, st;
13833      long tc;
13834 {
13835     char buf[MSG_SIZ];
13836     int seconds;
13837
13838     if( timeControl_2 > 0 ) {
13839         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13840             tc = timeControl_2;
13841         }
13842     }
13843     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13844     inc /= cps->timeOdds;
13845     st  /= cps->timeOdds;
13846
13847     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13848
13849     if (st > 0) {
13850       /* Set exact time per move, normally using st command */
13851       if (cps->stKludge) {
13852         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13853         seconds = st % 60;
13854         if (seconds == 0) {
13855           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13856         } else {
13857           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13858         }
13859       } else {
13860         snprintf(buf, MSG_SIZ, "st %d\n", st);
13861       }
13862     } else {
13863       /* Set conventional or incremental time control, using level command */
13864       if (seconds == 0) {
13865         /* Note old gnuchess bug -- minutes:seconds used to not work.
13866            Fixed in later versions, but still avoid :seconds
13867            when seconds is 0. */
13868         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13869       } else {
13870         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13871                  seconds, inc/1000.);
13872       }
13873     }
13874     SendToProgram(buf, cps);
13875
13876     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13877     /* Orthogonally, limit search to given depth */
13878     if (sd > 0) {
13879       if (cps->sdKludge) {
13880         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13881       } else {
13882         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13883       }
13884       SendToProgram(buf, cps);
13885     }
13886
13887     if(cps->nps >= 0) { /* [HGM] nps */
13888         if(cps->supportsNPS == FALSE)
13889           cps->nps = -1; // don't use if engine explicitly says not supported!
13890         else {
13891           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13892           SendToProgram(buf, cps);
13893         }
13894     }
13895 }
13896
13897 ChessProgramState *WhitePlayer()
13898 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13899 {
13900     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13901        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13902         return &second;
13903     return &first;
13904 }
13905
13906 void
13907 SendTimeRemaining(cps, machineWhite)
13908      ChessProgramState *cps;
13909      int /*boolean*/ machineWhite;
13910 {
13911     char message[MSG_SIZ];
13912     long time, otime;
13913
13914     /* Note: this routine must be called when the clocks are stopped
13915        or when they have *just* been set or switched; otherwise
13916        it will be off by the time since the current tick started.
13917     */
13918     if (machineWhite) {
13919         time = whiteTimeRemaining / 10;
13920         otime = blackTimeRemaining / 10;
13921     } else {
13922         time = blackTimeRemaining / 10;
13923         otime = whiteTimeRemaining / 10;
13924     }
13925     /* [HGM] translate opponent's time by time-odds factor */
13926     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13927     if (appData.debugMode) {
13928         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13929     }
13930
13931     if (time <= 0) time = 1;
13932     if (otime <= 0) otime = 1;
13933
13934     snprintf(message, MSG_SIZ, "time %ld\n", time);
13935     SendToProgram(message, cps);
13936
13937     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13938     SendToProgram(message, cps);
13939 }
13940
13941 int
13942 BoolFeature(p, name, loc, cps)
13943      char **p;
13944      char *name;
13945      int *loc;
13946      ChessProgramState *cps;
13947 {
13948   char buf[MSG_SIZ];
13949   int len = strlen(name);
13950   int val;
13951
13952   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13953     (*p) += len + 1;
13954     sscanf(*p, "%d", &val);
13955     *loc = (val != 0);
13956     while (**p && **p != ' ')
13957       (*p)++;
13958     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13959     SendToProgram(buf, cps);
13960     return TRUE;
13961   }
13962   return FALSE;
13963 }
13964
13965 int
13966 IntFeature(p, name, loc, cps)
13967      char **p;
13968      char *name;
13969      int *loc;
13970      ChessProgramState *cps;
13971 {
13972   char buf[MSG_SIZ];
13973   int len = strlen(name);
13974   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13975     (*p) += len + 1;
13976     sscanf(*p, "%d", loc);
13977     while (**p && **p != ' ') (*p)++;
13978     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13979     SendToProgram(buf, cps);
13980     return TRUE;
13981   }
13982   return FALSE;
13983 }
13984
13985 int
13986 StringFeature(p, name, loc, cps)
13987      char **p;
13988      char *name;
13989      char loc[];
13990      ChessProgramState *cps;
13991 {
13992   char buf[MSG_SIZ];
13993   int len = strlen(name);
13994   if (strncmp((*p), name, len) == 0
13995       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13996     (*p) += len + 2;
13997     sscanf(*p, "%[^\"]", loc);
13998     while (**p && **p != '\"') (*p)++;
13999     if (**p == '\"') (*p)++;
14000     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14001     SendToProgram(buf, cps);
14002     return TRUE;
14003   }
14004   return FALSE;
14005 }
14006
14007 int
14008 ParseOption(Option *opt, ChessProgramState *cps)
14009 // [HGM] options: process the string that defines an engine option, and determine
14010 // name, type, default value, and allowed value range
14011 {
14012         char *p, *q, buf[MSG_SIZ];
14013         int n, min = (-1)<<31, max = 1<<31, def;
14014
14015         if(p = strstr(opt->name, " -spin ")) {
14016             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14017             if(max < min) max = min; // enforce consistency
14018             if(def < min) def = min;
14019             if(def > max) def = max;
14020             opt->value = def;
14021             opt->min = min;
14022             opt->max = max;
14023             opt->type = Spin;
14024         } else if((p = strstr(opt->name, " -slider "))) {
14025             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14026             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14027             if(max < min) max = min; // enforce consistency
14028             if(def < min) def = min;
14029             if(def > max) def = max;
14030             opt->value = def;
14031             opt->min = min;
14032             opt->max = max;
14033             opt->type = Spin; // Slider;
14034         } else if((p = strstr(opt->name, " -string "))) {
14035             opt->textValue = p+9;
14036             opt->type = TextBox;
14037         } else if((p = strstr(opt->name, " -file "))) {
14038             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14039             opt->textValue = p+7;
14040             opt->type = FileName; // FileName;
14041         } else if((p = strstr(opt->name, " -path "))) {
14042             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14043             opt->textValue = p+7;
14044             opt->type = PathName; // PathName;
14045         } else if(p = strstr(opt->name, " -check ")) {
14046             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14047             opt->value = (def != 0);
14048             opt->type = CheckBox;
14049         } else if(p = strstr(opt->name, " -combo ")) {
14050             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14051             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14052             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14053             opt->value = n = 0;
14054             while(q = StrStr(q, " /// ")) {
14055                 n++; *q = 0;    // count choices, and null-terminate each of them
14056                 q += 5;
14057                 if(*q == '*') { // remember default, which is marked with * prefix
14058                     q++;
14059                     opt->value = n;
14060                 }
14061                 cps->comboList[cps->comboCnt++] = q;
14062             }
14063             cps->comboList[cps->comboCnt++] = NULL;
14064             opt->max = n + 1;
14065             opt->type = ComboBox;
14066         } else if(p = strstr(opt->name, " -button")) {
14067             opt->type = Button;
14068         } else if(p = strstr(opt->name, " -save")) {
14069             opt->type = SaveButton;
14070         } else return FALSE;
14071         *p = 0; // terminate option name
14072         // now look if the command-line options define a setting for this engine option.
14073         if(cps->optionSettings && cps->optionSettings[0])
14074             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14075         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14076           snprintf(buf, MSG_SIZ, "option %s", p);
14077                 if(p = strstr(buf, ",")) *p = 0;
14078                 if(q = strchr(buf, '=')) switch(opt->type) {
14079                     case ComboBox:
14080                         for(n=0; n<opt->max; n++)
14081                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14082                         break;
14083                     case TextBox:
14084                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14085                         break;
14086                     case Spin:
14087                     case CheckBox:
14088                         opt->value = atoi(q+1);
14089                     default:
14090                         break;
14091                 }
14092                 strcat(buf, "\n");
14093                 SendToProgram(buf, cps);
14094         }
14095         return TRUE;
14096 }
14097
14098 void
14099 FeatureDone(cps, val)
14100      ChessProgramState* cps;
14101      int val;
14102 {
14103   DelayedEventCallback cb = GetDelayedEvent();
14104   if ((cb == InitBackEnd3 && cps == &first) ||
14105       (cb == SettingsMenuIfReady && cps == &second) ||
14106       (cb == TwoMachinesEventIfReady && cps == &second)) {
14107     CancelDelayedEvent();
14108     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14109   }
14110   cps->initDone = val;
14111 }
14112
14113 /* Parse feature command from engine */
14114 void
14115 ParseFeatures(args, cps)
14116      char* args;
14117      ChessProgramState *cps;
14118 {
14119   char *p = args;
14120   char *q;
14121   int val;
14122   char buf[MSG_SIZ];
14123
14124   for (;;) {
14125     while (*p == ' ') p++;
14126     if (*p == NULLCHAR) return;
14127
14128     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14129     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14130     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14131     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14132     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14133     if (BoolFeature(&p, "reuse", &val, cps)) {
14134       /* Engine can disable reuse, but can't enable it if user said no */
14135       if (!val) cps->reuse = FALSE;
14136       continue;
14137     }
14138     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14139     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14140       if (gameMode == TwoMachinesPlay) {
14141         DisplayTwoMachinesTitle();
14142       } else {
14143         DisplayTitle("");
14144       }
14145       continue;
14146     }
14147     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14148     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14149     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14150     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14151     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14152     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14153     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14154     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14155     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14156     if (IntFeature(&p, "done", &val, cps)) {
14157       FeatureDone(cps, val);
14158       continue;
14159     }
14160     /* Added by Tord: */
14161     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14162     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14163     /* End of additions by Tord */
14164
14165     /* [HGM] added features: */
14166     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14167     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14168     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14169     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14170     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14171     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14172     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14173         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14174           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14175             SendToProgram(buf, cps);
14176             continue;
14177         }
14178         if(cps->nrOptions >= MAX_OPTIONS) {
14179             cps->nrOptions--;
14180             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14181             DisplayError(buf, 0);
14182         }
14183         continue;
14184     }
14185     /* End of additions by HGM */
14186
14187     /* unknown feature: complain and skip */
14188     q = p;
14189     while (*q && *q != '=') q++;
14190     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14191     SendToProgram(buf, cps);
14192     p = q;
14193     if (*p == '=') {
14194       p++;
14195       if (*p == '\"') {
14196         p++;
14197         while (*p && *p != '\"') p++;
14198         if (*p == '\"') p++;
14199       } else {
14200         while (*p && *p != ' ') p++;
14201       }
14202     }
14203   }
14204
14205 }
14206
14207 void
14208 PeriodicUpdatesEvent(newState)
14209      int newState;
14210 {
14211     if (newState == appData.periodicUpdates)
14212       return;
14213
14214     appData.periodicUpdates=newState;
14215
14216     /* Display type changes, so update it now */
14217 //    DisplayAnalysis();
14218
14219     /* Get the ball rolling again... */
14220     if (newState) {
14221         AnalysisPeriodicEvent(1);
14222         StartAnalysisClock();
14223     }
14224 }
14225
14226 void
14227 PonderNextMoveEvent(newState)
14228      int newState;
14229 {
14230     if (newState == appData.ponderNextMove) return;
14231     if (gameMode == EditPosition) EditPositionDone(TRUE);
14232     if (newState) {
14233         SendToProgram("hard\n", &first);
14234         if (gameMode == TwoMachinesPlay) {
14235             SendToProgram("hard\n", &second);
14236         }
14237     } else {
14238         SendToProgram("easy\n", &first);
14239         thinkOutput[0] = NULLCHAR;
14240         if (gameMode == TwoMachinesPlay) {
14241             SendToProgram("easy\n", &second);
14242         }
14243     }
14244     appData.ponderNextMove = newState;
14245 }
14246
14247 void
14248 NewSettingEvent(option, feature, command, value)
14249      char *command;
14250      int option, value, *feature;
14251 {
14252     char buf[MSG_SIZ];
14253
14254     if (gameMode == EditPosition) EditPositionDone(TRUE);
14255     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14256     if(feature == NULL || *feature) SendToProgram(buf, &first);
14257     if (gameMode == TwoMachinesPlay) {
14258         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14259     }
14260 }
14261
14262 void
14263 ShowThinkingEvent()
14264 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14265 {
14266     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14267     int newState = appData.showThinking
14268         // [HGM] thinking: other features now need thinking output as well
14269         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14270
14271     if (oldState == newState) return;
14272     oldState = newState;
14273     if (gameMode == EditPosition) EditPositionDone(TRUE);
14274     if (oldState) {
14275         SendToProgram("post\n", &first);
14276         if (gameMode == TwoMachinesPlay) {
14277             SendToProgram("post\n", &second);
14278         }
14279     } else {
14280         SendToProgram("nopost\n", &first);
14281         thinkOutput[0] = NULLCHAR;
14282         if (gameMode == TwoMachinesPlay) {
14283             SendToProgram("nopost\n", &second);
14284         }
14285     }
14286 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14287 }
14288
14289 void
14290 AskQuestionEvent(title, question, replyPrefix, which)
14291      char *title; char *question; char *replyPrefix; char *which;
14292 {
14293   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14294   if (pr == NoProc) return;
14295   AskQuestion(title, question, replyPrefix, pr);
14296 }
14297
14298 void
14299 TypeInEvent(char firstChar)
14300 {
14301     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14302         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14303         gameMode == AnalyzeMode || gameMode == EditGame || \r
14304         gameMode == EditPosition || gameMode == IcsExamining ||\r
14305         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14306         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14307                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14308                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14309         gameMode == Training) PopUpMoveDialog(firstChar);
14310 }
14311
14312 void
14313 TypeInDoneEvent(char *move)
14314 {
14315         Board board;
14316         int n, fromX, fromY, toX, toY;
14317         char promoChar;
14318         ChessMove moveType;\r
14319
14320         // [HGM] FENedit\r
14321         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14322                 EditPositionPasteFEN(move);\r
14323                 return;\r
14324         }\r
14325         // [HGM] movenum: allow move number to be typed in any mode\r
14326         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14327           ToNrEvent(2*n-1);\r
14328           return;\r
14329         }\r
14330
14331       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14332         gameMode != Training) {\r
14333         DisplayMoveError(_("Displayed move is not current"));\r
14334       } else {\r
14335         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14336           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14337         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14338         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14339           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14340           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14341         } else {\r
14342           DisplayMoveError(_("Could not parse move"));\r
14343         }
14344       }\r
14345 }\r
14346
14347 void
14348 DisplayMove(moveNumber)
14349      int moveNumber;
14350 {
14351     char message[MSG_SIZ];
14352     char res[MSG_SIZ];
14353     char cpThinkOutput[MSG_SIZ];
14354
14355     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14356
14357     if (moveNumber == forwardMostMove - 1 ||
14358         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14359
14360         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14361
14362         if (strchr(cpThinkOutput, '\n')) {
14363             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14364         }
14365     } else {
14366         *cpThinkOutput = NULLCHAR;
14367     }
14368
14369     /* [AS] Hide thinking from human user */
14370     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14371         *cpThinkOutput = NULLCHAR;
14372         if( thinkOutput[0] != NULLCHAR ) {
14373             int i;
14374
14375             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14376                 cpThinkOutput[i] = '.';
14377             }
14378             cpThinkOutput[i] = NULLCHAR;
14379             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14380         }
14381     }
14382
14383     if (moveNumber == forwardMostMove - 1 &&
14384         gameInfo.resultDetails != NULL) {
14385         if (gameInfo.resultDetails[0] == NULLCHAR) {
14386           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14387         } else {
14388           snprintf(res, MSG_SIZ, " {%s} %s",
14389                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14390         }
14391     } else {
14392         res[0] = NULLCHAR;
14393     }
14394
14395     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14396         DisplayMessage(res, cpThinkOutput);
14397     } else {
14398       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14399                 WhiteOnMove(moveNumber) ? " " : ".. ",
14400                 parseList[moveNumber], res);
14401         DisplayMessage(message, cpThinkOutput);
14402     }
14403 }
14404
14405 void
14406 DisplayComment(moveNumber, text)
14407      int moveNumber;
14408      char *text;
14409 {
14410     char title[MSG_SIZ];
14411     char buf[8000]; // comment can be long!
14412     int score, depth;
14413
14414     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14415       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14416     } else {
14417       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14418               WhiteOnMove(moveNumber) ? " " : ".. ",
14419               parseList[moveNumber]);
14420     }
14421     // [HGM] PV info: display PV info together with (or as) comment
14422     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14423       if(text == NULL) text = "";
14424       score = pvInfoList[moveNumber].score;
14425       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14426               depth, (pvInfoList[moveNumber].time+50)/100, text);
14427       text = buf;
14428     }
14429     if (text != NULL && (appData.autoDisplayComment || commentUp))
14430         CommentPopUp(title, text);
14431 }
14432
14433 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14434  * might be busy thinking or pondering.  It can be omitted if your
14435  * gnuchess is configured to stop thinking immediately on any user
14436  * input.  However, that gnuchess feature depends on the FIONREAD
14437  * ioctl, which does not work properly on some flavors of Unix.
14438  */
14439 void
14440 Attention(cps)
14441      ChessProgramState *cps;
14442 {
14443 #if ATTENTION
14444     if (!cps->useSigint) return;
14445     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14446     switch (gameMode) {
14447       case MachinePlaysWhite:
14448       case MachinePlaysBlack:
14449       case TwoMachinesPlay:
14450       case IcsPlayingWhite:
14451       case IcsPlayingBlack:
14452       case AnalyzeMode:
14453       case AnalyzeFile:
14454         /* Skip if we know it isn't thinking */
14455         if (!cps->maybeThinking) return;
14456         if (appData.debugMode)
14457           fprintf(debugFP, "Interrupting %s\n", cps->which);
14458         InterruptChildProcess(cps->pr);
14459         cps->maybeThinking = FALSE;
14460         break;
14461       default:
14462         break;
14463     }
14464 #endif /*ATTENTION*/
14465 }
14466
14467 int
14468 CheckFlags()
14469 {
14470     if (whiteTimeRemaining <= 0) {
14471         if (!whiteFlag) {
14472             whiteFlag = TRUE;
14473             if (appData.icsActive) {
14474                 if (appData.autoCallFlag &&
14475                     gameMode == IcsPlayingBlack && !blackFlag) {
14476                   SendToICS(ics_prefix);
14477                   SendToICS("flag\n");
14478                 }
14479             } else {
14480                 if (blackFlag) {
14481                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14482                 } else {
14483                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14484                     if (appData.autoCallFlag) {
14485                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14486                         return TRUE;
14487                     }
14488                 }
14489             }
14490         }
14491     }
14492     if (blackTimeRemaining <= 0) {
14493         if (!blackFlag) {
14494             blackFlag = TRUE;
14495             if (appData.icsActive) {
14496                 if (appData.autoCallFlag &&
14497                     gameMode == IcsPlayingWhite && !whiteFlag) {
14498                   SendToICS(ics_prefix);
14499                   SendToICS("flag\n");
14500                 }
14501             } else {
14502                 if (whiteFlag) {
14503                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14504                 } else {
14505                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14506                     if (appData.autoCallFlag) {
14507                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14508                         return TRUE;
14509                     }
14510                 }
14511             }
14512         }
14513     }
14514     return FALSE;
14515 }
14516
14517 void
14518 CheckTimeControl()
14519 {
14520     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14521         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14522
14523     /*
14524      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14525      */
14526     if ( !WhiteOnMove(forwardMostMove) ) {
14527         /* White made time control */
14528         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14529         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14530         /* [HGM] time odds: correct new time quota for time odds! */
14531                                             / WhitePlayer()->timeOdds;
14532         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14533     } else {
14534         lastBlack -= blackTimeRemaining;
14535         /* Black made time control */
14536         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14537                                             / WhitePlayer()->other->timeOdds;
14538         lastWhite = whiteTimeRemaining;
14539     }
14540 }
14541
14542 void
14543 DisplayBothClocks()
14544 {
14545     int wom = gameMode == EditPosition ?
14546       !blackPlaysFirst : WhiteOnMove(currentMove);
14547     DisplayWhiteClock(whiteTimeRemaining, wom);
14548     DisplayBlackClock(blackTimeRemaining, !wom);
14549 }
14550
14551
14552 /* Timekeeping seems to be a portability nightmare.  I think everyone
14553    has ftime(), but I'm really not sure, so I'm including some ifdefs
14554    to use other calls if you don't.  Clocks will be less accurate if
14555    you have neither ftime nor gettimeofday.
14556 */
14557
14558 /* VS 2008 requires the #include outside of the function */
14559 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14560 #include <sys/timeb.h>
14561 #endif
14562
14563 /* Get the current time as a TimeMark */
14564 void
14565 GetTimeMark(tm)
14566      TimeMark *tm;
14567 {
14568 #if HAVE_GETTIMEOFDAY
14569
14570     struct timeval timeVal;
14571     struct timezone timeZone;
14572
14573     gettimeofday(&timeVal, &timeZone);
14574     tm->sec = (long) timeVal.tv_sec;
14575     tm->ms = (int) (timeVal.tv_usec / 1000L);
14576
14577 #else /*!HAVE_GETTIMEOFDAY*/
14578 #if HAVE_FTIME
14579
14580 // include <sys/timeb.h> / moved to just above start of function
14581     struct timeb timeB;
14582
14583     ftime(&timeB);
14584     tm->sec = (long) timeB.time;
14585     tm->ms = (int) timeB.millitm;
14586
14587 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14588     tm->sec = (long) time(NULL);
14589     tm->ms = 0;
14590 #endif
14591 #endif
14592 }
14593
14594 /* Return the difference in milliseconds between two
14595    time marks.  We assume the difference will fit in a long!
14596 */
14597 long
14598 SubtractTimeMarks(tm2, tm1)
14599      TimeMark *tm2, *tm1;
14600 {
14601     return 1000L*(tm2->sec - tm1->sec) +
14602            (long) (tm2->ms - tm1->ms);
14603 }
14604
14605
14606 /*
14607  * Code to manage the game clocks.
14608  *
14609  * In tournament play, black starts the clock and then white makes a move.
14610  * We give the human user a slight advantage if he is playing white---the
14611  * clocks don't run until he makes his first move, so it takes zero time.
14612  * Also, we don't account for network lag, so we could get out of sync
14613  * with GNU Chess's clock -- but then, referees are always right.
14614  */
14615
14616 static TimeMark tickStartTM;
14617 static long intendedTickLength;
14618
14619 long
14620 NextTickLength(timeRemaining)
14621      long timeRemaining;
14622 {
14623     long nominalTickLength, nextTickLength;
14624
14625     if (timeRemaining > 0L && timeRemaining <= 10000L)
14626       nominalTickLength = 100L;
14627     else
14628       nominalTickLength = 1000L;
14629     nextTickLength = timeRemaining % nominalTickLength;
14630     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14631
14632     return nextTickLength;
14633 }
14634
14635 /* Adjust clock one minute up or down */
14636 void
14637 AdjustClock(Boolean which, int dir)
14638 {
14639     if(which) blackTimeRemaining += 60000*dir;
14640     else      whiteTimeRemaining += 60000*dir;
14641     DisplayBothClocks();
14642 }
14643
14644 /* Stop clocks and reset to a fresh time control */
14645 void
14646 ResetClocks()
14647 {
14648     (void) StopClockTimer();
14649     if (appData.icsActive) {
14650         whiteTimeRemaining = blackTimeRemaining = 0;
14651     } else if (searchTime) {
14652         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14653         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14654     } else { /* [HGM] correct new time quote for time odds */
14655         whiteTC = blackTC = fullTimeControlString;
14656         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14657         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14658     }
14659     if (whiteFlag || blackFlag) {
14660         DisplayTitle("");
14661         whiteFlag = blackFlag = FALSE;
14662     }
14663     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14664     DisplayBothClocks();
14665 }
14666
14667 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14668
14669 /* Decrement running clock by amount of time that has passed */
14670 void
14671 DecrementClocks()
14672 {
14673     long timeRemaining;
14674     long lastTickLength, fudge;
14675     TimeMark now;
14676
14677     if (!appData.clockMode) return;
14678     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14679
14680     GetTimeMark(&now);
14681
14682     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14683
14684     /* Fudge if we woke up a little too soon */
14685     fudge = intendedTickLength - lastTickLength;
14686     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14687
14688     if (WhiteOnMove(forwardMostMove)) {
14689         if(whiteNPS >= 0) lastTickLength = 0;
14690         timeRemaining = whiteTimeRemaining -= lastTickLength;
14691         if(timeRemaining < 0 && !appData.icsActive) {
14692             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14693             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14694                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14695                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14696             }
14697         }
14698         DisplayWhiteClock(whiteTimeRemaining - fudge,
14699                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14700     } else {
14701         if(blackNPS >= 0) lastTickLength = 0;
14702         timeRemaining = blackTimeRemaining -= lastTickLength;
14703         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14704             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14705             if(suddenDeath) {
14706                 blackStartMove = forwardMostMove;
14707                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14708             }
14709         }
14710         DisplayBlackClock(blackTimeRemaining - fudge,
14711                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14712     }
14713     if (CheckFlags()) return;
14714
14715     tickStartTM = now;
14716     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14717     StartClockTimer(intendedTickLength);
14718
14719     /* if the time remaining has fallen below the alarm threshold, sound the
14720      * alarm. if the alarm has sounded and (due to a takeback or time control
14721      * with increment) the time remaining has increased to a level above the
14722      * threshold, reset the alarm so it can sound again.
14723      */
14724
14725     if (appData.icsActive && appData.icsAlarm) {
14726
14727         /* make sure we are dealing with the user's clock */
14728         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14729                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14730            )) return;
14731
14732         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14733             alarmSounded = FALSE;
14734         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14735             PlayAlarmSound();
14736             alarmSounded = TRUE;
14737         }
14738     }
14739 }
14740
14741
14742 /* A player has just moved, so stop the previously running
14743    clock and (if in clock mode) start the other one.
14744    We redisplay both clocks in case we're in ICS mode, because
14745    ICS gives us an update to both clocks after every move.
14746    Note that this routine is called *after* forwardMostMove
14747    is updated, so the last fractional tick must be subtracted
14748    from the color that is *not* on move now.
14749 */
14750 void
14751 SwitchClocks(int newMoveNr)
14752 {
14753     long lastTickLength;
14754     TimeMark now;
14755     int flagged = FALSE;
14756
14757     GetTimeMark(&now);
14758
14759     if (StopClockTimer() && appData.clockMode) {
14760         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14761         if (!WhiteOnMove(forwardMostMove)) {
14762             if(blackNPS >= 0) lastTickLength = 0;
14763             blackTimeRemaining -= lastTickLength;
14764            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14765 //         if(pvInfoList[forwardMostMove].time == -1)
14766                  pvInfoList[forwardMostMove].time =               // use GUI time
14767                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14768         } else {
14769            if(whiteNPS >= 0) lastTickLength = 0;
14770            whiteTimeRemaining -= lastTickLength;
14771            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14772 //         if(pvInfoList[forwardMostMove].time == -1)
14773                  pvInfoList[forwardMostMove].time =
14774                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14775         }
14776         flagged = CheckFlags();
14777     }
14778     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14779     CheckTimeControl();
14780
14781     if (flagged || !appData.clockMode) return;
14782
14783     switch (gameMode) {
14784       case MachinePlaysBlack:
14785       case MachinePlaysWhite:
14786       case BeginningOfGame:
14787         if (pausing) return;
14788         break;
14789
14790       case EditGame:
14791       case PlayFromGameFile:
14792       case IcsExamining:
14793         return;
14794
14795       default:
14796         break;
14797     }
14798
14799     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14800         if(WhiteOnMove(forwardMostMove))
14801              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14802         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14803     }
14804
14805     tickStartTM = now;
14806     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14807       whiteTimeRemaining : blackTimeRemaining);
14808     StartClockTimer(intendedTickLength);
14809 }
14810
14811
14812 /* Stop both clocks */
14813 void
14814 StopClocks()
14815 {
14816     long lastTickLength;
14817     TimeMark now;
14818
14819     if (!StopClockTimer()) return;
14820     if (!appData.clockMode) return;
14821
14822     GetTimeMark(&now);
14823
14824     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14825     if (WhiteOnMove(forwardMostMove)) {
14826         if(whiteNPS >= 0) lastTickLength = 0;
14827         whiteTimeRemaining -= lastTickLength;
14828         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14829     } else {
14830         if(blackNPS >= 0) lastTickLength = 0;
14831         blackTimeRemaining -= lastTickLength;
14832         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14833     }
14834     CheckFlags();
14835 }
14836
14837 /* Start clock of player on move.  Time may have been reset, so
14838    if clock is already running, stop and restart it. */
14839 void
14840 StartClocks()
14841 {
14842     (void) StopClockTimer(); /* in case it was running already */
14843     DisplayBothClocks();
14844     if (CheckFlags()) return;
14845
14846     if (!appData.clockMode) return;
14847     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14848
14849     GetTimeMark(&tickStartTM);
14850     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14851       whiteTimeRemaining : blackTimeRemaining);
14852
14853    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14854     whiteNPS = blackNPS = -1;
14855     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14856        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14857         whiteNPS = first.nps;
14858     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14859        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14860         blackNPS = first.nps;
14861     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14862         whiteNPS = second.nps;
14863     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14864         blackNPS = second.nps;
14865     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14866
14867     StartClockTimer(intendedTickLength);
14868 }
14869
14870 char *
14871 TimeString(ms)
14872      long ms;
14873 {
14874     long second, minute, hour, day;
14875     char *sign = "";
14876     static char buf[32];
14877
14878     if (ms > 0 && ms <= 9900) {
14879       /* convert milliseconds to tenths, rounding up */
14880       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14881
14882       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14883       return buf;
14884     }
14885
14886     /* convert milliseconds to seconds, rounding up */
14887     /* use floating point to avoid strangeness of integer division
14888        with negative dividends on many machines */
14889     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14890
14891     if (second < 0) {
14892         sign = "-";
14893         second = -second;
14894     }
14895
14896     day = second / (60 * 60 * 24);
14897     second = second % (60 * 60 * 24);
14898     hour = second / (60 * 60);
14899     second = second % (60 * 60);
14900     minute = second / 60;
14901     second = second % 60;
14902
14903     if (day > 0)
14904       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14905               sign, day, hour, minute, second);
14906     else if (hour > 0)
14907       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14908     else
14909       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14910
14911     return buf;
14912 }
14913
14914
14915 /*
14916  * This is necessary because some C libraries aren't ANSI C compliant yet.
14917  */
14918 char *
14919 StrStr(string, match)
14920      char *string, *match;
14921 {
14922     int i, length;
14923
14924     length = strlen(match);
14925
14926     for (i = strlen(string) - length; i >= 0; i--, string++)
14927       if (!strncmp(match, string, length))
14928         return string;
14929
14930     return NULL;
14931 }
14932
14933 char *
14934 StrCaseStr(string, match)
14935      char *string, *match;
14936 {
14937     int i, j, length;
14938
14939     length = strlen(match);
14940
14941     for (i = strlen(string) - length; i >= 0; i--, string++) {
14942         for (j = 0; j < length; j++) {
14943             if (ToLower(match[j]) != ToLower(string[j]))
14944               break;
14945         }
14946         if (j == length) return string;
14947     }
14948
14949     return NULL;
14950 }
14951
14952 #ifndef _amigados
14953 int
14954 StrCaseCmp(s1, s2)
14955      char *s1, *s2;
14956 {
14957     char c1, c2;
14958
14959     for (;;) {
14960         c1 = ToLower(*s1++);
14961         c2 = ToLower(*s2++);
14962         if (c1 > c2) return 1;
14963         if (c1 < c2) return -1;
14964         if (c1 == NULLCHAR) return 0;
14965     }
14966 }
14967
14968
14969 int
14970 ToLower(c)
14971      int c;
14972 {
14973     return isupper(c) ? tolower(c) : c;
14974 }
14975
14976
14977 int
14978 ToUpper(c)
14979      int c;
14980 {
14981     return islower(c) ? toupper(c) : c;
14982 }
14983 #endif /* !_amigados    */
14984
14985 char *
14986 StrSave(s)
14987      char *s;
14988 {
14989   char *ret;
14990
14991   if ((ret = (char *) malloc(strlen(s) + 1)))
14992     {
14993       safeStrCpy(ret, s, strlen(s)+1);
14994     }
14995   return ret;
14996 }
14997
14998 char *
14999 StrSavePtr(s, savePtr)
15000      char *s, **savePtr;
15001 {
15002     if (*savePtr) {
15003         free(*savePtr);
15004     }
15005     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15006       safeStrCpy(*savePtr, s, strlen(s)+1);
15007     }
15008     return(*savePtr);
15009 }
15010
15011 char *
15012 PGNDate()
15013 {
15014     time_t clock;
15015     struct tm *tm;
15016     char buf[MSG_SIZ];
15017
15018     clock = time((time_t *)NULL);
15019     tm = localtime(&clock);
15020     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15021             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15022     return StrSave(buf);
15023 }
15024
15025
15026 char *
15027 PositionToFEN(move, overrideCastling)
15028      int move;
15029      char *overrideCastling;
15030 {
15031     int i, j, fromX, fromY, toX, toY;
15032     int whiteToPlay;
15033     char buf[128];
15034     char *p, *q;
15035     int emptycount;
15036     ChessSquare piece;
15037
15038     whiteToPlay = (gameMode == EditPosition) ?
15039       !blackPlaysFirst : (move % 2 == 0);
15040     p = buf;
15041
15042     /* Piece placement data */
15043     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15044         emptycount = 0;
15045         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15046             if (boards[move][i][j] == EmptySquare) {
15047                 emptycount++;
15048             } else { ChessSquare piece = boards[move][i][j];
15049                 if (emptycount > 0) {
15050                     if(emptycount<10) /* [HGM] can be >= 10 */
15051                         *p++ = '0' + emptycount;
15052                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15053                     emptycount = 0;
15054                 }
15055                 if(PieceToChar(piece) == '+') {
15056                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15057                     *p++ = '+';
15058                     piece = (ChessSquare)(DEMOTED piece);
15059                 }
15060                 *p++ = PieceToChar(piece);
15061                 if(p[-1] == '~') {
15062                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15063                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15064                     *p++ = '~';
15065                 }
15066             }
15067         }
15068         if (emptycount > 0) {
15069             if(emptycount<10) /* [HGM] can be >= 10 */
15070                 *p++ = '0' + emptycount;
15071             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15072             emptycount = 0;
15073         }
15074         *p++ = '/';
15075     }
15076     *(p - 1) = ' ';
15077
15078     /* [HGM] print Crazyhouse or Shogi holdings */
15079     if( gameInfo.holdingsWidth ) {
15080         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15081         q = p;
15082         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15083             piece = boards[move][i][BOARD_WIDTH-1];
15084             if( piece != EmptySquare )
15085               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15086                   *p++ = PieceToChar(piece);
15087         }
15088         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15089             piece = boards[move][BOARD_HEIGHT-i-1][0];
15090             if( piece != EmptySquare )
15091               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15092                   *p++ = PieceToChar(piece);
15093         }
15094
15095         if( q == p ) *p++ = '-';
15096         *p++ = ']';
15097         *p++ = ' ';
15098     }
15099
15100     /* Active color */
15101     *p++ = whiteToPlay ? 'w' : 'b';
15102     *p++ = ' ';
15103
15104   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15105     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15106   } else {
15107   if(nrCastlingRights) {
15108      q = p;
15109      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15110        /* [HGM] write directly from rights */
15111            if(boards[move][CASTLING][2] != NoRights &&
15112               boards[move][CASTLING][0] != NoRights   )
15113                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15114            if(boards[move][CASTLING][2] != NoRights &&
15115               boards[move][CASTLING][1] != NoRights   )
15116                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15117            if(boards[move][CASTLING][5] != NoRights &&
15118               boards[move][CASTLING][3] != NoRights   )
15119                 *p++ = boards[move][CASTLING][3] + AAA;
15120            if(boards[move][CASTLING][5] != NoRights &&
15121               boards[move][CASTLING][4] != NoRights   )
15122                 *p++ = boards[move][CASTLING][4] + AAA;
15123      } else {
15124
15125         /* [HGM] write true castling rights */
15126         if( nrCastlingRights == 6 ) {
15127             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15128                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15129             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15130                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15131             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15132                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15133             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15134                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15135         }
15136      }
15137      if (q == p) *p++ = '-'; /* No castling rights */
15138      *p++ = ' ';
15139   }
15140
15141   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15142      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15143     /* En passant target square */
15144     if (move > backwardMostMove) {
15145         fromX = moveList[move - 1][0] - AAA;
15146         fromY = moveList[move - 1][1] - ONE;
15147         toX = moveList[move - 1][2] - AAA;
15148         toY = moveList[move - 1][3] - ONE;
15149         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15150             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15151             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15152             fromX == toX) {
15153             /* 2-square pawn move just happened */
15154             *p++ = toX + AAA;
15155             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15156         } else {
15157             *p++ = '-';
15158         }
15159     } else if(move == backwardMostMove) {
15160         // [HGM] perhaps we should always do it like this, and forget the above?
15161         if((signed char)boards[move][EP_STATUS] >= 0) {
15162             *p++ = boards[move][EP_STATUS] + AAA;
15163             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15164         } else {
15165             *p++ = '-';
15166         }
15167     } else {
15168         *p++ = '-';
15169     }
15170     *p++ = ' ';
15171   }
15172   }
15173
15174     /* [HGM] find reversible plies */
15175     {   int i = 0, j=move;
15176
15177         if (appData.debugMode) { int k;
15178             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15179             for(k=backwardMostMove; k<=forwardMostMove; k++)
15180                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15181
15182         }
15183
15184         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15185         if( j == backwardMostMove ) i += initialRulePlies;
15186         sprintf(p, "%d ", i);
15187         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15188     }
15189     /* Fullmove number */
15190     sprintf(p, "%d", (move / 2) + 1);
15191
15192     return StrSave(buf);
15193 }
15194
15195 Boolean
15196 ParseFEN(board, blackPlaysFirst, fen)
15197     Board board;
15198      int *blackPlaysFirst;
15199      char *fen;
15200 {
15201     int i, j;
15202     char *p, c;
15203     int emptycount;
15204     ChessSquare piece;
15205
15206     p = fen;
15207
15208     /* [HGM] by default clear Crazyhouse holdings, if present */
15209     if(gameInfo.holdingsWidth) {
15210        for(i=0; i<BOARD_HEIGHT; i++) {
15211            board[i][0]             = EmptySquare; /* black holdings */
15212            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15213            board[i][1]             = (ChessSquare) 0; /* black counts */
15214            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15215        }
15216     }
15217
15218     /* Piece placement data */
15219     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15220         j = 0;
15221         for (;;) {
15222             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15223                 if (*p == '/') p++;
15224                 emptycount = gameInfo.boardWidth - j;
15225                 while (emptycount--)
15226                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15227                 break;
15228 #if(BOARD_FILES >= 10)
15229             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15230                 p++; emptycount=10;
15231                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15232                 while (emptycount--)
15233                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15234 #endif
15235             } else if (isdigit(*p)) {
15236                 emptycount = *p++ - '0';
15237                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15238                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15239                 while (emptycount--)
15240                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15241             } else if (*p == '+' || isalpha(*p)) {
15242                 if (j >= gameInfo.boardWidth) return FALSE;
15243                 if(*p=='+') {
15244                     piece = CharToPiece(*++p);
15245                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15246                     piece = (ChessSquare) (PROMOTED piece ); p++;
15247                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15248                 } else piece = CharToPiece(*p++);
15249
15250                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15251                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15252                     piece = (ChessSquare) (PROMOTED piece);
15253                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15254                     p++;
15255                 }
15256                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15257             } else {
15258                 return FALSE;
15259             }
15260         }
15261     }
15262     while (*p == '/' || *p == ' ') p++;
15263
15264     /* [HGM] look for Crazyhouse holdings here */
15265     while(*p==' ') p++;
15266     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15267         if(*p == '[') p++;
15268         if(*p == '-' ) p++; /* empty holdings */ else {
15269             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15270             /* if we would allow FEN reading to set board size, we would   */
15271             /* have to add holdings and shift the board read so far here   */
15272             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15273                 p++;
15274                 if((int) piece >= (int) BlackPawn ) {
15275                     i = (int)piece - (int)BlackPawn;
15276                     i = PieceToNumber((ChessSquare)i);
15277                     if( i >= gameInfo.holdingsSize ) return FALSE;
15278                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15279                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15280                 } else {
15281                     i = (int)piece - (int)WhitePawn;
15282                     i = PieceToNumber((ChessSquare)i);
15283                     if( i >= gameInfo.holdingsSize ) return FALSE;
15284                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15285                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15286                 }
15287             }
15288         }
15289         if(*p == ']') p++;
15290     }
15291
15292     while(*p == ' ') p++;
15293
15294     /* Active color */
15295     c = *p++;
15296     if(appData.colorNickNames) {
15297       if( c == appData.colorNickNames[0] ) c = 'w'; else
15298       if( c == appData.colorNickNames[1] ) c = 'b';
15299     }
15300     switch (c) {
15301       case 'w':
15302         *blackPlaysFirst = FALSE;
15303         break;
15304       case 'b':
15305         *blackPlaysFirst = TRUE;
15306         break;
15307       default:
15308         return FALSE;
15309     }
15310
15311     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15312     /* return the extra info in global variiables             */
15313
15314     /* set defaults in case FEN is incomplete */
15315     board[EP_STATUS] = EP_UNKNOWN;
15316     for(i=0; i<nrCastlingRights; i++ ) {
15317         board[CASTLING][i] =
15318             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15319     }   /* assume possible unless obviously impossible */
15320     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15321     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15322     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15323                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15324     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15325     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15326     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15327                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15328     FENrulePlies = 0;
15329
15330     while(*p==' ') p++;
15331     if(nrCastlingRights) {
15332       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15333           /* castling indicator present, so default becomes no castlings */
15334           for(i=0; i<nrCastlingRights; i++ ) {
15335                  board[CASTLING][i] = NoRights;
15336           }
15337       }
15338       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15339              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15340              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15341              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15342         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15343
15344         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15345             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15346             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15347         }
15348         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15349             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15350         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15351                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15352         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15353                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15354         switch(c) {
15355           case'K':
15356               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15357               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15358               board[CASTLING][2] = whiteKingFile;
15359               break;
15360           case'Q':
15361               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15362               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15363               board[CASTLING][2] = whiteKingFile;
15364               break;
15365           case'k':
15366               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15367               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15368               board[CASTLING][5] = blackKingFile;
15369               break;
15370           case'q':
15371               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15372               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15373               board[CASTLING][5] = blackKingFile;
15374           case '-':
15375               break;
15376           default: /* FRC castlings */
15377               if(c >= 'a') { /* black rights */
15378                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15379                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15380                   if(i == BOARD_RGHT) break;
15381                   board[CASTLING][5] = i;
15382                   c -= AAA;
15383                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15384                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15385                   if(c > i)
15386                       board[CASTLING][3] = c;
15387                   else
15388                       board[CASTLING][4] = c;
15389               } else { /* white rights */
15390                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15391                     if(board[0][i] == WhiteKing) break;
15392                   if(i == BOARD_RGHT) break;
15393                   board[CASTLING][2] = i;
15394                   c -= AAA - 'a' + 'A';
15395                   if(board[0][c] >= WhiteKing) break;
15396                   if(c > i)
15397                       board[CASTLING][0] = c;
15398                   else
15399                       board[CASTLING][1] = c;
15400               }
15401         }
15402       }
15403       for(i=0; i<nrCastlingRights; i++)
15404         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15405     if (appData.debugMode) {
15406         fprintf(debugFP, "FEN castling rights:");
15407         for(i=0; i<nrCastlingRights; i++)
15408         fprintf(debugFP, " %d", board[CASTLING][i]);
15409         fprintf(debugFP, "\n");
15410     }
15411
15412       while(*p==' ') p++;
15413     }
15414
15415     /* read e.p. field in games that know e.p. capture */
15416     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15417        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15418       if(*p=='-') {
15419         p++; board[EP_STATUS] = EP_NONE;
15420       } else {
15421          char c = *p++ - AAA;
15422
15423          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15424          if(*p >= '0' && *p <='9') p++;
15425          board[EP_STATUS] = c;
15426       }
15427     }
15428
15429
15430     if(sscanf(p, "%d", &i) == 1) {
15431         FENrulePlies = i; /* 50-move ply counter */
15432         /* (The move number is still ignored)    */
15433     }
15434
15435     return TRUE;
15436 }
15437
15438 void
15439 EditPositionPasteFEN(char *fen)
15440 {
15441   if (fen != NULL) {
15442     Board initial_position;
15443
15444     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15445       DisplayError(_("Bad FEN position in clipboard"), 0);
15446       return ;
15447     } else {
15448       int savedBlackPlaysFirst = blackPlaysFirst;
15449       EditPositionEvent();
15450       blackPlaysFirst = savedBlackPlaysFirst;
15451       CopyBoard(boards[0], initial_position);
15452       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15453       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15454       DisplayBothClocks();
15455       DrawPosition(FALSE, boards[currentMove]);
15456     }
15457   }
15458 }
15459
15460 static char cseq[12] = "\\   ";
15461
15462 Boolean set_cont_sequence(char *new_seq)
15463 {
15464     int len;
15465     Boolean ret;
15466
15467     // handle bad attempts to set the sequence
15468         if (!new_seq)
15469                 return 0; // acceptable error - no debug
15470
15471     len = strlen(new_seq);
15472     ret = (len > 0) && (len < sizeof(cseq));
15473     if (ret)
15474       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15475     else if (appData.debugMode)
15476       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15477     return ret;
15478 }
15479
15480 /*
15481     reformat a source message so words don't cross the width boundary.  internal
15482     newlines are not removed.  returns the wrapped size (no null character unless
15483     included in source message).  If dest is NULL, only calculate the size required
15484     for the dest buffer.  lp argument indicats line position upon entry, and it's
15485     passed back upon exit.
15486 */
15487 int wrap(char *dest, char *src, int count, int width, int *lp)
15488 {
15489     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15490
15491     cseq_len = strlen(cseq);
15492     old_line = line = *lp;
15493     ansi = len = clen = 0;
15494
15495     for (i=0; i < count; i++)
15496     {
15497         if (src[i] == '\033')
15498             ansi = 1;
15499
15500         // if we hit the width, back up
15501         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15502         {
15503             // store i & len in case the word is too long
15504             old_i = i, old_len = len;
15505
15506             // find the end of the last word
15507             while (i && src[i] != ' ' && src[i] != '\n')
15508             {
15509                 i--;
15510                 len--;
15511             }
15512
15513             // word too long?  restore i & len before splitting it
15514             if ((old_i-i+clen) >= width)
15515             {
15516                 i = old_i;
15517                 len = old_len;
15518             }
15519
15520             // extra space?
15521             if (i && src[i-1] == ' ')
15522                 len--;
15523
15524             if (src[i] != ' ' && src[i] != '\n')
15525             {
15526                 i--;
15527                 if (len)
15528                     len--;
15529             }
15530
15531             // now append the newline and continuation sequence
15532             if (dest)
15533                 dest[len] = '\n';
15534             len++;
15535             if (dest)
15536                 strncpy(dest+len, cseq, cseq_len);
15537             len += cseq_len;
15538             line = cseq_len;
15539             clen = cseq_len;
15540             continue;
15541         }
15542
15543         if (dest)
15544             dest[len] = src[i];
15545         len++;
15546         if (!ansi)
15547             line++;
15548         if (src[i] == '\n')
15549             line = 0;
15550         if (src[i] == 'm')
15551             ansi = 0;
15552     }
15553     if (dest && appData.debugMode)
15554     {
15555         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15556             count, width, line, len, *lp);
15557         show_bytes(debugFP, src, count);
15558         fprintf(debugFP, "\ndest: ");
15559         show_bytes(debugFP, dest, len);
15560         fprintf(debugFP, "\n");
15561     }
15562     *lp = dest ? line : old_line;
15563
15564     return len;
15565 }
15566
15567 // [HGM] vari: routines for shelving variations
15568
15569 void
15570 PushTail(int firstMove, int lastMove)
15571 {
15572         int i, j, nrMoves = lastMove - firstMove;
15573
15574         if(appData.icsActive) { // only in local mode
15575                 forwardMostMove = currentMove; // mimic old ICS behavior
15576                 return;
15577         }
15578         if(storedGames >= MAX_VARIATIONS-1) return;
15579
15580         // push current tail of game on stack
15581         savedResult[storedGames] = gameInfo.result;
15582         savedDetails[storedGames] = gameInfo.resultDetails;
15583         gameInfo.resultDetails = NULL;
15584         savedFirst[storedGames] = firstMove;
15585         savedLast [storedGames] = lastMove;
15586         savedFramePtr[storedGames] = framePtr;
15587         framePtr -= nrMoves; // reserve space for the boards
15588         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15589             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15590             for(j=0; j<MOVE_LEN; j++)
15591                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15592             for(j=0; j<2*MOVE_LEN; j++)
15593                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15594             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15595             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15596             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15597             pvInfoList[firstMove+i-1].depth = 0;
15598             commentList[framePtr+i] = commentList[firstMove+i];
15599             commentList[firstMove+i] = NULL;
15600         }
15601
15602         storedGames++;
15603         forwardMostMove = firstMove; // truncate game so we can start variation
15604         if(storedGames == 1) GreyRevert(FALSE);
15605 }
15606
15607 Boolean
15608 PopTail(Boolean annotate)
15609 {
15610         int i, j, nrMoves;
15611         char buf[8000], moveBuf[20];
15612
15613         if(appData.icsActive) return FALSE; // only in local mode
15614         if(!storedGames) return FALSE; // sanity
15615         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15616
15617         storedGames--;
15618         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15619         nrMoves = savedLast[storedGames] - currentMove;
15620         if(annotate) {
15621                 int cnt = 10;
15622                 if(!WhiteOnMove(currentMove))
15623                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15624                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15625                 for(i=currentMove; i<forwardMostMove; i++) {
15626                         if(WhiteOnMove(i))
15627                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15628                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15629                         strcat(buf, moveBuf);
15630                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15631                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15632                 }
15633                 strcat(buf, ")");
15634         }
15635         for(i=1; i<=nrMoves; i++) { // copy last variation back
15636             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15637             for(j=0; j<MOVE_LEN; j++)
15638                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15639             for(j=0; j<2*MOVE_LEN; j++)
15640                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15641             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15642             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15643             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15644             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15645             commentList[currentMove+i] = commentList[framePtr+i];
15646             commentList[framePtr+i] = NULL;
15647         }
15648         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15649         framePtr = savedFramePtr[storedGames];
15650         gameInfo.result = savedResult[storedGames];
15651         if(gameInfo.resultDetails != NULL) {
15652             free(gameInfo.resultDetails);
15653       }
15654         gameInfo.resultDetails = savedDetails[storedGames];
15655         forwardMostMove = currentMove + nrMoves;
15656         if(storedGames == 0) GreyRevert(TRUE);
15657         return TRUE;
15658 }
15659
15660 void
15661 CleanupTail()
15662 {       // remove all shelved variations
15663         int i;
15664         for(i=0; i<storedGames; i++) {
15665             if(savedDetails[i])
15666                 free(savedDetails[i]);
15667             savedDetails[i] = NULL;
15668         }
15669         for(i=framePtr; i<MAX_MOVES; i++) {
15670                 if(commentList[i]) free(commentList[i]);
15671                 commentList[i] = NULL;
15672         }
15673         framePtr = MAX_MOVES-1;
15674         storedGames = 0;
15675 }
15676
15677 void
15678 LoadVariation(int index, char *text)
15679 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15680         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15681         int level = 0, move;
15682
15683         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15684         // first find outermost bracketing variation
15685         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15686             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15687                 if(*p == '{') wait = '}'; else
15688                 if(*p == '[') wait = ']'; else
15689                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15690                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15691             }
15692             if(*p == wait) wait = NULLCHAR; // closing ]} found
15693             p++;
15694         }
15695         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15696         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15697         end[1] = NULLCHAR; // clip off comment beyond variation
15698         ToNrEvent(currentMove-1);
15699         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15700         // kludge: use ParsePV() to append variation to game
15701         move = currentMove;
15702         ParsePV(start, TRUE);
15703         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15704         ClearPremoveHighlights();
15705         CommentPopDown();
15706         ToNrEvent(currentMove+1);
15707 }
15708