fcc611a1412ae90f3ba446b7fa71243c64da38a4
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy( char *dst, const char *src, size_t count )
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble(u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags(index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
527 };
528
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
534 };
535
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
541 };
542
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackMan, BlackFerz,
547         BlackKing, BlackMan, BlackKnight, BlackRook }
548 };
549
550
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
557 };
558
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
564 };
565
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
571 };
572
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
578 };
579
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
585 };
586
587 #ifdef GOTHIC
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
593 };
594 #else // !GOTHIC
595 #define GothicArray CapablancaArray
596 #endif // !GOTHIC
597
598 #ifdef FALCON
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !FALCON
606 #define FalconArray CapablancaArray
607 #endif // !FALCON
608
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
615
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 };
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
626
627
628 Board initialPosition;
629
630
631 /* Convert str to a rating. Checks for special cases of "----",
632
633    "++++", etc. Also strips ()'s */
634 int
635 string_to_rating(str)
636   char *str;
637 {
638   while(*str && !isdigit(*str)) ++str;
639   if (!*str)
640     return 0;   /* One of the special "no rating" cases */
641   else
642     return atoi(str);
643 }
644
645 void
646 ClearProgramStats()
647 {
648     /* Init programStats */
649     programStats.movelist[0] = 0;
650     programStats.depth = 0;
651     programStats.nr_moves = 0;
652     programStats.moves_left = 0;
653     programStats.nodes = 0;
654     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
655     programStats.score = 0;
656     programStats.got_only_move = 0;
657     programStats.got_fail = 0;
658     programStats.line_is_book = 0;
659 }
660
661 void
662 CommonEngineInit()
663 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664     if (appData.firstPlaysBlack) {
665         first.twoMachinesColor = "black\n";
666         second.twoMachinesColor = "white\n";
667     } else {
668         first.twoMachinesColor = "white\n";
669         second.twoMachinesColor = "black\n";
670     }
671
672     first.other = &second;
673     second.other = &first;
674
675     { float norm = 1;
676         if(appData.timeOddsMode) {
677             norm = appData.timeOdds[0];
678             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
679         }
680         first.timeOdds  = appData.timeOdds[0]/norm;
681         second.timeOdds = appData.timeOdds[1]/norm;
682     }
683
684     if(programVersion) free(programVersion);
685     if (appData.noChessProgram) {
686         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687         sprintf(programVersion, "%s", PACKAGE_STRING);
688     } else {
689       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
692     }
693 }
694
695 void
696 ClearOptions(ChessProgramState *cps)
697 {
698     int i;
699     cps->nrOptions = cps->comboCnt = 0;
700     for(i=0; i<MAX_OPTIONS; i++) {
701         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
702         cps->option[i].textValue = 0;
703     }
704 }
705
706 char *engineNames[] = {
707 "first",
708 "second"
709 };
710
711 InitEngine(ChessProgramState *cps, int n)
712 {   // [HGM] all engine initialiation put in a function that does one engine
713
714     ClearOptions(cps);
715
716     cps->which = engineNames[n];
717     cps->maybeThinking = FALSE;
718     cps->pr = NoProc;
719     cps->isr = NULL;
720     cps->sendTime = 2;
721     cps->sendDrawOffers = 1;
722
723     cps->program = appData.chessProgram[n];
724     cps->host = appData.host[n];
725     cps->dir = appData.directory[n];
726     cps->initString = appData.engInitString[n];
727     cps->computerString = appData.computerString[n];
728     cps->useSigint  = TRUE;
729     cps->useSigterm = TRUE;
730     cps->reuse = appData.reuse[n];
731     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
732     cps->useSetboard = FALSE;
733     cps->useSAN = FALSE;
734     cps->usePing = FALSE;
735     cps->lastPing = 0;
736     cps->lastPong = 0;
737     cps->usePlayother = FALSE;
738     cps->useColors = TRUE;
739     cps->useUsermove = FALSE;
740     cps->sendICS = FALSE;
741     cps->sendName = appData.icsActive;
742     cps->sdKludge = FALSE;
743     cps->stKludge = FALSE;
744     TidyProgramName(cps->program, cps->host, cps->tidy);
745     cps->matchWins = 0;
746     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
747     cps->analysisSupport = 2; /* detect */
748     cps->analyzing = FALSE;
749     cps->initDone = FALSE;
750
751     /* New features added by Tord: */
752     cps->useFEN960 = FALSE;
753     cps->useOOCastle = TRUE;
754     /* End of new features added by Tord. */
755     cps->fenOverride  = appData.fenOverride[n];
756
757     /* [HGM] time odds: set factor for each machine */
758     cps->timeOdds  = appData.timeOdds[n];
759
760     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
761     cps->accumulateTC = appData.accumulateTC[n];
762     cps->maxNrOfSessions = 1;
763
764     /* [HGM] debug */
765     cps->debug = FALSE;
766     cps->supportsNPS = UNKNOWN;
767
768     /* [HGM] options */
769     cps->optionSettings  = appData.engOptions[n];
770
771     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
772     cps->isUCI = appData.isUCI[n]; /* [AS] */
773     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
774
775     if (appData.protocolVersion[n] > PROTOVER
776         || appData.protocolVersion[n] < 1)
777       {
778         char buf[MSG_SIZ];
779         int len;
780
781         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
782                        appData.protocolVersion[n]);
783         if( (len > MSG_SIZ) && appData.debugMode )
784           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
785
786         DisplayFatalError(buf, 0, 2);
787       }
788     else
789       {
790         cps->protocolVersion = appData.protocolVersion[n];
791       }
792
793     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
794 }
795
796 void
797 InitBackEnd1()
798 {
799     int matched, min, sec;
800
801     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
802     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
803
804     GetTimeMark(&programStartTime);
805     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
806
807     ClearProgramStats();
808     programStats.ok_to_send = 1;
809     programStats.seen_stat = 0;
810
811     /*
812      * Initialize game list
813      */
814     ListNew(&gameList);
815
816
817     /*
818      * Internet chess server status
819      */
820     if (appData.icsActive) {
821         appData.matchMode = FALSE;
822         appData.matchGames = 0;
823 #if ZIPPY
824         appData.noChessProgram = !appData.zippyPlay;
825 #else
826         appData.zippyPlay = FALSE;
827         appData.zippyTalk = FALSE;
828         appData.noChessProgram = TRUE;
829 #endif
830         if (*appData.icsHelper != NULLCHAR) {
831             appData.useTelnet = TRUE;
832             appData.telnetProgram = appData.icsHelper;
833         }
834     } else {
835         appData.zippyTalk = appData.zippyPlay = FALSE;
836     }
837
838     /* [AS] Initialize pv info list [HGM] and game state */
839     {
840         int i, j;
841
842         for( i=0; i<=framePtr; i++ ) {
843             pvInfoList[i].depth = -1;
844             boards[i][EP_STATUS] = EP_NONE;
845             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
846         }
847     }
848
849     /*
850      * Parse timeControl resource
851      */
852     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
853                           appData.movesPerSession)) {
854         char buf[MSG_SIZ];
855         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
856         DisplayFatalError(buf, 0, 2);
857     }
858
859     /*
860      * Parse searchTime resource
861      */
862     if (*appData.searchTime != NULLCHAR) {
863         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
864         if (matched == 1) {
865             searchTime = min * 60;
866         } else if (matched == 2) {
867             searchTime = min * 60 + sec;
868         } else {
869             char buf[MSG_SIZ];
870             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
871             DisplayFatalError(buf, 0, 2);
872         }
873     }
874
875     /* [AS] Adjudication threshold */
876     adjudicateLossThreshold = appData.adjudicateLossThreshold;
877
878     InitEngine(&first, 0);
879     InitEngine(&second, 1);
880     CommonEngineInit();
881
882     if (appData.icsActive) {
883         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
884     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
885         appData.clockMode = FALSE;
886         first.sendTime = second.sendTime = 0;
887     }
888
889 #if ZIPPY
890     /* Override some settings from environment variables, for backward
891        compatibility.  Unfortunately it's not feasible to have the env
892        vars just set defaults, at least in xboard.  Ugh.
893     */
894     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
895       ZippyInit();
896     }
897 #endif
898
899     if (!appData.icsActive) {
900       char buf[MSG_SIZ];
901       int len;
902
903       /* Check for variants that are supported only in ICS mode,
904          or not at all.  Some that are accepted here nevertheless
905          have bugs; see comments below.
906       */
907       VariantClass variant = StringToVariant(appData.variant);
908       switch (variant) {
909       case VariantBughouse:     /* need four players and two boards */
910       case VariantKriegspiel:   /* need to hide pieces and move details */
911         /* case VariantFischeRandom: (Fabien: moved below) */
912         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
913         if( (len > MSG_SIZ) && appData.debugMode )
914           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
915
916         DisplayFatalError(buf, 0, 2);
917         return;
918
919       case VariantUnknown:
920       case VariantLoadable:
921       case Variant29:
922       case Variant30:
923       case Variant31:
924       case Variant32:
925       case Variant33:
926       case Variant34:
927       case Variant35:
928       case Variant36:
929       default:
930         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
931         if( (len > MSG_SIZ) && appData.debugMode )
932           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
933
934         DisplayFatalError(buf, 0, 2);
935         return;
936
937       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
938       case VariantFairy:      /* [HGM] TestLegality definitely off! */
939       case VariantGothic:     /* [HGM] should work */
940       case VariantCapablanca: /* [HGM] should work */
941       case VariantCourier:    /* [HGM] initial forced moves not implemented */
942       case VariantShogi:      /* [HGM] could still mate with pawn drop */
943       case VariantKnightmate: /* [HGM] should work */
944       case VariantCylinder:   /* [HGM] untested */
945       case VariantFalcon:     /* [HGM] untested */
946       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
947                                  offboard interposition not understood */
948       case VariantNormal:     /* definitely works! */
949       case VariantWildCastle: /* pieces not automatically shuffled */
950       case VariantNoCastle:   /* pieces not automatically shuffled */
951       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
952       case VariantLosers:     /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantSuicide:    /* should work except for win condition,
955                                  and doesn't know captures are mandatory */
956       case VariantGiveaway:   /* should work except for win condition,
957                                  and doesn't know captures are mandatory */
958       case VariantTwoKings:   /* should work */
959       case VariantAtomic:     /* should work except for win condition */
960       case Variant3Check:     /* should work except for win condition */
961       case VariantShatranj:   /* should work except for all win conditions */
962       case VariantMakruk:     /* should work except for daw countdown */
963       case VariantBerolina:   /* might work if TestLegality is off */
964       case VariantCapaRandom: /* should work */
965       case VariantJanus:      /* should work */
966       case VariantSuper:      /* experimental */
967       case VariantGreat:      /* experimental, requires legality testing to be off */
968       case VariantSChess:     /* S-Chess, should work */
969       case VariantSpartan:    /* should work */
970         break;
971       }
972     }
973
974 }
975
976 int NextIntegerFromString( char ** str, long * value )
977 {
978     int result = -1;
979     char * s = *str;
980
981     while( *s == ' ' || *s == '\t' ) {
982         s++;
983     }
984
985     *value = 0;
986
987     if( *s >= '0' && *s <= '9' ) {
988         while( *s >= '0' && *s <= '9' ) {
989             *value = *value * 10 + (*s - '0');
990             s++;
991         }
992
993         result = 0;
994     }
995
996     *str = s;
997
998     return result;
999 }
1000
1001 int NextTimeControlFromString( char ** str, long * value )
1002 {
1003     long temp;
1004     int result = NextIntegerFromString( str, &temp );
1005
1006     if( result == 0 ) {
1007         *value = temp * 60; /* Minutes */
1008         if( **str == ':' ) {
1009             (*str)++;
1010             result = NextIntegerFromString( str, &temp );
1011             *value += temp; /* Seconds */
1012         }
1013     }
1014
1015     return result;
1016 }
1017
1018 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1019 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1020     int result = -1, type = 0; long temp, temp2;
1021
1022     if(**str != ':') return -1; // old params remain in force!
1023     (*str)++;
1024     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1025     if( NextIntegerFromString( str, &temp ) ) return -1;
1026     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1027
1028     if(**str != '/') {
1029         /* time only: incremental or sudden-death time control */
1030         if(**str == '+') { /* increment follows; read it */
1031             (*str)++;
1032             if(**str == '!') type = *(*str)++; // Bronstein TC
1033             if(result = NextIntegerFromString( str, &temp2)) return -1;
1034             *inc = temp2 * 1000;
1035             if(**str == '.') { // read fraction of increment
1036                 char *start = ++(*str);
1037                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1038                 temp2 *= 1000;
1039                 while(start++ < *str) temp2 /= 10;
1040                 *inc += temp2;
1041             }
1042         } else *inc = 0;
1043         *moves = 0; *tc = temp * 1000; *incType = type;
1044         return 0;
1045     }
1046
1047     (*str)++; /* classical time control */
1048     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1049
1050     if(result == 0) {
1051         *moves = temp;
1052         *tc    = temp2 * 1000;
1053         *inc   = 0;
1054         *incType = type;
1055     }
1056     return result;
1057 }
1058
1059 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1060 {   /* [HGM] get time to add from the multi-session time-control string */
1061     int incType, moves=1; /* kludge to force reading of first session */
1062     long time, increment;
1063     char *s = tcString;
1064
1065     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1066     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1067     do {
1068         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1069         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1070         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1071         if(movenr == -1) return time;    /* last move before new session     */
1072         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1073         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1074         if(!moves) return increment;     /* current session is incremental   */
1075         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1076     } while(movenr >= -1);               /* try again for next session       */
1077
1078     return 0; // no new time quota on this move
1079 }
1080
1081 int
1082 ParseTimeControl(tc, ti, mps)
1083      char *tc;
1084      float ti;
1085      int mps;
1086 {
1087   long tc1;
1088   long tc2;
1089   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1090   int min, sec=0;
1091
1092   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1093   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1094       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1095   if(ti > 0) {
1096
1097     if(mps)
1098       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1099     else 
1100       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1101   } else {
1102     if(mps)
1103       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1104     else 
1105       snprintf(buf, MSG_SIZ, ":%s", mytc);
1106   }
1107   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1108   
1109   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1110     return FALSE;
1111   }
1112
1113   if( *tc == '/' ) {
1114     /* Parse second time control */
1115     tc++;
1116
1117     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1118       return FALSE;
1119     }
1120
1121     if( tc2 == 0 ) {
1122       return FALSE;
1123     }
1124
1125     timeControl_2 = tc2 * 1000;
1126   }
1127   else {
1128     timeControl_2 = 0;
1129   }
1130
1131   if( tc1 == 0 ) {
1132     return FALSE;
1133   }
1134
1135   timeControl = tc1 * 1000;
1136
1137   if (ti >= 0) {
1138     timeIncrement = ti * 1000;  /* convert to ms */
1139     movesPerSession = 0;
1140   } else {
1141     timeIncrement = 0;
1142     movesPerSession = mps;
1143   }
1144   return TRUE;
1145 }
1146
1147 void
1148 InitBackEnd2()
1149 {
1150     if (appData.debugMode) {
1151         fprintf(debugFP, "%s\n", programVersion);
1152     }
1153
1154     set_cont_sequence(appData.wrapContSeq);
1155     if (appData.matchGames > 0) {
1156         appData.matchMode = TRUE;
1157     } else if (appData.matchMode) {
1158         appData.matchGames = 1;
1159     }
1160     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1161         appData.matchGames = appData.sameColorGames;
1162     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1163         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1164         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1165     }
1166     Reset(TRUE, FALSE);
1167     if (appData.noChessProgram || first.protocolVersion == 1) {
1168       InitBackEnd3();
1169     } else {
1170       /* kludge: allow timeout for initial "feature" commands */
1171       FreezeUI();
1172       DisplayMessage("", _("Starting chess program"));
1173       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1174     }
1175 }
1176
1177 void
1178 MatchEvent(int mode)
1179 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1180         /* Set up machine vs. machine match */
1181         if (appData.noChessProgram) {
1182             DisplayFatalError(_("Can't have a match with no chess programs"),
1183                               0, 2);
1184             return;
1185         }
1186         matchMode = mode;
1187         matchGame = 1;
1188         if (*appData.loadGameFile != NULLCHAR) {
1189             int index = appData.loadGameIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadGameFromFile(appData.loadGameFile,
1192                                   index,
1193                                   appData.loadGameFile, FALSE)) {
1194                 DisplayFatalError(_("Bad game file"), 0, 1);
1195                 return;
1196             }
1197         } else if (*appData.loadPositionFile != NULLCHAR) {
1198             int index = appData.loadPositionIndex; // [HGM] autoinc
1199             if(index<0) lastIndex = index = 1;
1200             if (!LoadPositionFromFile(appData.loadPositionFile,
1201                                       index,
1202                                       appData.loadPositionFile)) {
1203                 DisplayFatalError(_("Bad position file"), 0, 1);
1204                 return;
1205             }
1206         }
1207         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1208         TwoMachinesEvent();
1209 }
1210
1211 void
1212 InitBackEnd3 P((void))
1213 {
1214     GameMode initialMode;
1215     char buf[MSG_SIZ];
1216     int err, len;
1217
1218     InitChessProgram(&first, startedFromSetupPosition);
1219
1220     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1221         free(programVersion);
1222         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1223         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1224     }
1225
1226     if (appData.icsActive) {
1227 #ifdef WIN32
1228         /* [DM] Make a console window if needed [HGM] merged ifs */
1229         ConsoleCreate();
1230 #endif
1231         err = establish();
1232         if (err != 0)
1233           {
1234             if (*appData.icsCommPort != NULLCHAR)
1235               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1236                              appData.icsCommPort);
1237             else
1238               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1239                         appData.icsHost, appData.icsPort);
1240
1241             if( (len > MSG_SIZ) && appData.debugMode )
1242               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1243
1244             DisplayFatalError(buf, err, 1);
1245             return;
1246         }
1247         SetICSMode();
1248         telnetISR =
1249           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1250         fromUserISR =
1251           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1252         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1253             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1254     } else if (appData.noChessProgram) {
1255         SetNCPMode();
1256     } else {
1257         SetGNUMode();
1258     }
1259
1260     if (*appData.cmailGameName != NULLCHAR) {
1261         SetCmailMode();
1262         OpenLoopback(&cmailPR);
1263         cmailISR =
1264           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1265     }
1266
1267     ThawUI();
1268     DisplayMessage("", "");
1269     if (StrCaseCmp(appData.initialMode, "") == 0) {
1270       initialMode = BeginningOfGame;
1271     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1272       initialMode = TwoMachinesPlay;
1273     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1274       initialMode = AnalyzeFile;
1275     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1276       initialMode = AnalyzeMode;
1277     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1278       initialMode = MachinePlaysWhite;
1279     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1280       initialMode = MachinePlaysBlack;
1281     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1282       initialMode = EditGame;
1283     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1284       initialMode = EditPosition;
1285     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1286       initialMode = Training;
1287     } else {
1288       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1289       if( (len > MSG_SIZ) && appData.debugMode )
1290         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1291
1292       DisplayFatalError(buf, 0, 2);
1293       return;
1294     }
1295
1296     if (appData.matchMode) {
1297         MatchEvent(TRUE);
1298     } else if (*appData.cmailGameName != NULLCHAR) {
1299         /* Set up cmail mode */
1300         ReloadCmailMsgEvent(TRUE);
1301     } else {
1302         /* Set up other modes */
1303         if (initialMode == AnalyzeFile) {
1304           if (*appData.loadGameFile == NULLCHAR) {
1305             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1306             return;
1307           }
1308         }
1309         if (*appData.loadGameFile != NULLCHAR) {
1310             (void) LoadGameFromFile(appData.loadGameFile,
1311                                     appData.loadGameIndex,
1312                                     appData.loadGameFile, TRUE);
1313         } else if (*appData.loadPositionFile != NULLCHAR) {
1314             (void) LoadPositionFromFile(appData.loadPositionFile,
1315                                         appData.loadPositionIndex,
1316                                         appData.loadPositionFile);
1317             /* [HGM] try to make self-starting even after FEN load */
1318             /* to allow automatic setup of fairy variants with wtm */
1319             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1320                 gameMode = BeginningOfGame;
1321                 setboardSpoiledMachineBlack = 1;
1322             }
1323             /* [HGM] loadPos: make that every new game uses the setup */
1324             /* from file as long as we do not switch variant          */
1325             if(!blackPlaysFirst) {
1326                 startedFromPositionFile = TRUE;
1327                 CopyBoard(filePosition, boards[0]);
1328             }
1329         }
1330         if (initialMode == AnalyzeMode) {
1331           if (appData.noChessProgram) {
1332             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1333             return;
1334           }
1335           if (appData.icsActive) {
1336             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1337             return;
1338           }
1339           AnalyzeModeEvent();
1340         } else if (initialMode == AnalyzeFile) {
1341           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1342           ShowThinkingEvent();
1343           AnalyzeFileEvent();
1344           AnalysisPeriodicEvent(1);
1345         } else if (initialMode == MachinePlaysWhite) {
1346           if (appData.noChessProgram) {
1347             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1348                               0, 2);
1349             return;
1350           }
1351           if (appData.icsActive) {
1352             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1353                               0, 2);
1354             return;
1355           }
1356           MachineWhiteEvent();
1357         } else if (initialMode == MachinePlaysBlack) {
1358           if (appData.noChessProgram) {
1359             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1360                               0, 2);
1361             return;
1362           }
1363           if (appData.icsActive) {
1364             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1365                               0, 2);
1366             return;
1367           }
1368           MachineBlackEvent();
1369         } else if (initialMode == TwoMachinesPlay) {
1370           if (appData.noChessProgram) {
1371             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1372                               0, 2);
1373             return;
1374           }
1375           if (appData.icsActive) {
1376             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1377                               0, 2);
1378             return;
1379           }
1380           TwoMachinesEvent();
1381         } else if (initialMode == EditGame) {
1382           EditGameEvent();
1383         } else if (initialMode == EditPosition) {
1384           EditPositionEvent();
1385         } else if (initialMode == Training) {
1386           if (*appData.loadGameFile == NULLCHAR) {
1387             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1388             return;
1389           }
1390           TrainingEvent();
1391         }
1392     }
1393 }
1394
1395 /*
1396  * Establish will establish a contact to a remote host.port.
1397  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1398  *  used to talk to the host.
1399  * Returns 0 if okay, error code if not.
1400  */
1401 int
1402 establish()
1403 {
1404     char buf[MSG_SIZ];
1405
1406     if (*appData.icsCommPort != NULLCHAR) {
1407         /* Talk to the host through a serial comm port */
1408         return OpenCommPort(appData.icsCommPort, &icsPR);
1409
1410     } else if (*appData.gateway != NULLCHAR) {
1411         if (*appData.remoteShell == NULLCHAR) {
1412             /* Use the rcmd protocol to run telnet program on a gateway host */
1413             snprintf(buf, sizeof(buf), "%s %s %s",
1414                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1415             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1416
1417         } else {
1418             /* Use the rsh program to run telnet program on a gateway host */
1419             if (*appData.remoteUser == NULLCHAR) {
1420                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1421                         appData.gateway, appData.telnetProgram,
1422                         appData.icsHost, appData.icsPort);
1423             } else {
1424                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1425                         appData.remoteShell, appData.gateway,
1426                         appData.remoteUser, appData.telnetProgram,
1427                         appData.icsHost, appData.icsPort);
1428             }
1429             return StartChildProcess(buf, "", &icsPR);
1430
1431         }
1432     } else if (appData.useTelnet) {
1433         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1434
1435     } else {
1436         /* TCP socket interface differs somewhat between
1437            Unix and NT; handle details in the front end.
1438            */
1439         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1440     }
1441 }
1442
1443 void EscapeExpand(char *p, char *q)
1444 {       // [HGM] initstring: routine to shape up string arguments
1445         while(*p++ = *q++) if(p[-1] == '\\')
1446             switch(*q++) {
1447                 case 'n': p[-1] = '\n'; break;
1448                 case 'r': p[-1] = '\r'; break;
1449                 case 't': p[-1] = '\t'; break;
1450                 case '\\': p[-1] = '\\'; break;
1451                 case 0: *p = 0; return;
1452                 default: p[-1] = q[-1]; break;
1453             }
1454 }
1455
1456 void
1457 show_bytes(fp, buf, count)
1458      FILE *fp;
1459      char *buf;
1460      int count;
1461 {
1462     while (count--) {
1463         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1464             fprintf(fp, "\\%03o", *buf & 0xff);
1465         } else {
1466             putc(*buf, fp);
1467         }
1468         buf++;
1469     }
1470     fflush(fp);
1471 }
1472
1473 /* Returns an errno value */
1474 int
1475 OutputMaybeTelnet(pr, message, count, outError)
1476      ProcRef pr;
1477      char *message;
1478      int count;
1479      int *outError;
1480 {
1481     char buf[8192], *p, *q, *buflim;
1482     int left, newcount, outcount;
1483
1484     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1485         *appData.gateway != NULLCHAR) {
1486         if (appData.debugMode) {
1487             fprintf(debugFP, ">ICS: ");
1488             show_bytes(debugFP, message, count);
1489             fprintf(debugFP, "\n");
1490         }
1491         return OutputToProcess(pr, message, count, outError);
1492     }
1493
1494     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1495     p = message;
1496     q = buf;
1497     left = count;
1498     newcount = 0;
1499     while (left) {
1500         if (q >= buflim) {
1501             if (appData.debugMode) {
1502                 fprintf(debugFP, ">ICS: ");
1503                 show_bytes(debugFP, buf, newcount);
1504                 fprintf(debugFP, "\n");
1505             }
1506             outcount = OutputToProcess(pr, buf, newcount, outError);
1507             if (outcount < newcount) return -1; /* to be sure */
1508             q = buf;
1509             newcount = 0;
1510         }
1511         if (*p == '\n') {
1512             *q++ = '\r';
1513             newcount++;
1514         } else if (((unsigned char) *p) == TN_IAC) {
1515             *q++ = (char) TN_IAC;
1516             newcount ++;
1517         }
1518         *q++ = *p++;
1519         newcount++;
1520         left--;
1521     }
1522     if (appData.debugMode) {
1523         fprintf(debugFP, ">ICS: ");
1524         show_bytes(debugFP, buf, newcount);
1525         fprintf(debugFP, "\n");
1526     }
1527     outcount = OutputToProcess(pr, buf, newcount, outError);
1528     if (outcount < newcount) return -1; /* to be sure */
1529     return count;
1530 }
1531
1532 void
1533 read_from_player(isr, closure, message, count, error)
1534      InputSourceRef isr;
1535      VOIDSTAR closure;
1536      char *message;
1537      int count;
1538      int error;
1539 {
1540     int outError, outCount;
1541     static int gotEof = 0;
1542
1543     /* Pass data read from player on to ICS */
1544     if (count > 0) {
1545         gotEof = 0;
1546         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1547         if (outCount < count) {
1548             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1549         }
1550     } else if (count < 0) {
1551         RemoveInputSource(isr);
1552         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1553     } else if (gotEof++ > 0) {
1554         RemoveInputSource(isr);
1555         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1556     }
1557 }
1558
1559 void
1560 KeepAlive()
1561 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1562     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1563     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1564     SendToICS("date\n");
1565     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1566 }
1567
1568 /* added routine for printf style output to ics */
1569 void ics_printf(char *format, ...)
1570 {
1571     char buffer[MSG_SIZ];
1572     va_list args;
1573
1574     va_start(args, format);
1575     vsnprintf(buffer, sizeof(buffer), format, args);
1576     buffer[sizeof(buffer)-1] = '\0';
1577     SendToICS(buffer);
1578     va_end(args);
1579 }
1580
1581 void
1582 SendToICS(s)
1583      char *s;
1584 {
1585     int count, outCount, outError;
1586
1587     if (icsPR == NULL) return;
1588
1589     count = strlen(s);
1590     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1591     if (outCount < count) {
1592         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1593     }
1594 }
1595
1596 /* This is used for sending logon scripts to the ICS. Sending
1597    without a delay causes problems when using timestamp on ICC
1598    (at least on my machine). */
1599 void
1600 SendToICSDelayed(s,msdelay)
1601      char *s;
1602      long msdelay;
1603 {
1604     int count, outCount, outError;
1605
1606     if (icsPR == NULL) return;
1607
1608     count = strlen(s);
1609     if (appData.debugMode) {
1610         fprintf(debugFP, ">ICS: ");
1611         show_bytes(debugFP, s, count);
1612         fprintf(debugFP, "\n");
1613     }
1614     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1615                                       msdelay);
1616     if (outCount < count) {
1617         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1618     }
1619 }
1620
1621
1622 /* Remove all highlighting escape sequences in s
1623    Also deletes any suffix starting with '('
1624    */
1625 char *
1626 StripHighlightAndTitle(s)
1627      char *s;
1628 {
1629     static char retbuf[MSG_SIZ];
1630     char *p = retbuf;
1631
1632     while (*s != NULLCHAR) {
1633         while (*s == '\033') {
1634             while (*s != NULLCHAR && !isalpha(*s)) s++;
1635             if (*s != NULLCHAR) s++;
1636         }
1637         while (*s != NULLCHAR && *s != '\033') {
1638             if (*s == '(' || *s == '[') {
1639                 *p = NULLCHAR;
1640                 return retbuf;
1641             }
1642             *p++ = *s++;
1643         }
1644     }
1645     *p = NULLCHAR;
1646     return retbuf;
1647 }
1648
1649 /* Remove all highlighting escape sequences in s */
1650 char *
1651 StripHighlight(s)
1652      char *s;
1653 {
1654     static char retbuf[MSG_SIZ];
1655     char *p = retbuf;
1656
1657     while (*s != NULLCHAR) {
1658         while (*s == '\033') {
1659             while (*s != NULLCHAR && !isalpha(*s)) s++;
1660             if (*s != NULLCHAR) s++;
1661         }
1662         while (*s != NULLCHAR && *s != '\033') {
1663             *p++ = *s++;
1664         }
1665     }
1666     *p = NULLCHAR;
1667     return retbuf;
1668 }
1669
1670 char *variantNames[] = VARIANT_NAMES;
1671 char *
1672 VariantName(v)
1673      VariantClass v;
1674 {
1675     return variantNames[v];
1676 }
1677
1678
1679 /* Identify a variant from the strings the chess servers use or the
1680    PGN Variant tag names we use. */
1681 VariantClass
1682 StringToVariant(e)
1683      char *e;
1684 {
1685     char *p;
1686     int wnum = -1;
1687     VariantClass v = VariantNormal;
1688     int i, found = FALSE;
1689     char buf[MSG_SIZ];
1690     int len;
1691
1692     if (!e) return v;
1693
1694     /* [HGM] skip over optional board-size prefixes */
1695     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1696         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1697         while( *e++ != '_');
1698     }
1699
1700     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1701         v = VariantNormal;
1702         found = TRUE;
1703     } else
1704     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1705       if (StrCaseStr(e, variantNames[i])) {
1706         v = (VariantClass) i;
1707         found = TRUE;
1708         break;
1709       }
1710     }
1711
1712     if (!found) {
1713       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1714           || StrCaseStr(e, "wild/fr")
1715           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1716         v = VariantFischeRandom;
1717       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1718                  (i = 1, p = StrCaseStr(e, "w"))) {
1719         p += i;
1720         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1721         if (isdigit(*p)) {
1722           wnum = atoi(p);
1723         } else {
1724           wnum = -1;
1725         }
1726         switch (wnum) {
1727         case 0: /* FICS only, actually */
1728         case 1:
1729           /* Castling legal even if K starts on d-file */
1730           v = VariantWildCastle;
1731           break;
1732         case 2:
1733         case 3:
1734         case 4:
1735           /* Castling illegal even if K & R happen to start in
1736              normal positions. */
1737           v = VariantNoCastle;
1738           break;
1739         case 5:
1740         case 7:
1741         case 8:
1742         case 10:
1743         case 11:
1744         case 12:
1745         case 13:
1746         case 14:
1747         case 15:
1748         case 18:
1749         case 19:
1750           /* Castling legal iff K & R start in normal positions */
1751           v = VariantNormal;
1752           break;
1753         case 6:
1754         case 20:
1755         case 21:
1756           /* Special wilds for position setup; unclear what to do here */
1757           v = VariantLoadable;
1758           break;
1759         case 9:
1760           /* Bizarre ICC game */
1761           v = VariantTwoKings;
1762           break;
1763         case 16:
1764           v = VariantKriegspiel;
1765           break;
1766         case 17:
1767           v = VariantLosers;
1768           break;
1769         case 22:
1770           v = VariantFischeRandom;
1771           break;
1772         case 23:
1773           v = VariantCrazyhouse;
1774           break;
1775         case 24:
1776           v = VariantBughouse;
1777           break;
1778         case 25:
1779           v = Variant3Check;
1780           break;
1781         case 26:
1782           /* Not quite the same as FICS suicide! */
1783           v = VariantGiveaway;
1784           break;
1785         case 27:
1786           v = VariantAtomic;
1787           break;
1788         case 28:
1789           v = VariantShatranj;
1790           break;
1791
1792         /* Temporary names for future ICC types.  The name *will* change in
1793            the next xboard/WinBoard release after ICC defines it. */
1794         case 29:
1795           v = Variant29;
1796           break;
1797         case 30:
1798           v = Variant30;
1799           break;
1800         case 31:
1801           v = Variant31;
1802           break;
1803         case 32:
1804           v = Variant32;
1805           break;
1806         case 33:
1807           v = Variant33;
1808           break;
1809         case 34:
1810           v = Variant34;
1811           break;
1812         case 35:
1813           v = Variant35;
1814           break;
1815         case 36:
1816           v = Variant36;
1817           break;
1818         case 37:
1819           v = VariantShogi;
1820           break;
1821         case 38:
1822           v = VariantXiangqi;
1823           break;
1824         case 39:
1825           v = VariantCourier;
1826           break;
1827         case 40:
1828           v = VariantGothic;
1829           break;
1830         case 41:
1831           v = VariantCapablanca;
1832           break;
1833         case 42:
1834           v = VariantKnightmate;
1835           break;
1836         case 43:
1837           v = VariantFairy;
1838           break;
1839         case 44:
1840           v = VariantCylinder;
1841           break;
1842         case 45:
1843           v = VariantFalcon;
1844           break;
1845         case 46:
1846           v = VariantCapaRandom;
1847           break;
1848         case 47:
1849           v = VariantBerolina;
1850           break;
1851         case 48:
1852           v = VariantJanus;
1853           break;
1854         case 49:
1855           v = VariantSuper;
1856           break;
1857         case 50:
1858           v = VariantGreat;
1859           break;
1860         case -1:
1861           /* Found "wild" or "w" in the string but no number;
1862              must assume it's normal chess. */
1863           v = VariantNormal;
1864           break;
1865         default:
1866           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1867           if( (len > MSG_SIZ) && appData.debugMode )
1868             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1869
1870           DisplayError(buf, 0);
1871           v = VariantUnknown;
1872           break;
1873         }
1874       }
1875     }
1876     if (appData.debugMode) {
1877       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1878               e, wnum, VariantName(v));
1879     }
1880     return v;
1881 }
1882
1883 static int leftover_start = 0, leftover_len = 0;
1884 char star_match[STAR_MATCH_N][MSG_SIZ];
1885
1886 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1887    advance *index beyond it, and set leftover_start to the new value of
1888    *index; else return FALSE.  If pattern contains the character '*', it
1889    matches any sequence of characters not containing '\r', '\n', or the
1890    character following the '*' (if any), and the matched sequence(s) are
1891    copied into star_match.
1892    */
1893 int
1894 looking_at(buf, index, pattern)
1895      char *buf;
1896      int *index;
1897      char *pattern;
1898 {
1899     char *bufp = &buf[*index], *patternp = pattern;
1900     int star_count = 0;
1901     char *matchp = star_match[0];
1902
1903     for (;;) {
1904         if (*patternp == NULLCHAR) {
1905             *index = leftover_start = bufp - buf;
1906             *matchp = NULLCHAR;
1907             return TRUE;
1908         }
1909         if (*bufp == NULLCHAR) return FALSE;
1910         if (*patternp == '*') {
1911             if (*bufp == *(patternp + 1)) {
1912                 *matchp = NULLCHAR;
1913                 matchp = star_match[++star_count];
1914                 patternp += 2;
1915                 bufp++;
1916                 continue;
1917             } else if (*bufp == '\n' || *bufp == '\r') {
1918                 patternp++;
1919                 if (*patternp == NULLCHAR)
1920                   continue;
1921                 else
1922                   return FALSE;
1923             } else {
1924                 *matchp++ = *bufp++;
1925                 continue;
1926             }
1927         }
1928         if (*patternp != *bufp) return FALSE;
1929         patternp++;
1930         bufp++;
1931     }
1932 }
1933
1934 void
1935 SendToPlayer(data, length)
1936      char *data;
1937      int length;
1938 {
1939     int error, outCount;
1940     outCount = OutputToProcess(NoProc, data, length, &error);
1941     if (outCount < length) {
1942         DisplayFatalError(_("Error writing to display"), error, 1);
1943     }
1944 }
1945
1946 void
1947 PackHolding(packed, holding)
1948      char packed[];
1949      char *holding;
1950 {
1951     char *p = holding;
1952     char *q = packed;
1953     int runlength = 0;
1954     int curr = 9999;
1955     do {
1956         if (*p == curr) {
1957             runlength++;
1958         } else {
1959             switch (runlength) {
1960               case 0:
1961                 break;
1962               case 1:
1963                 *q++ = curr;
1964                 break;
1965               case 2:
1966                 *q++ = curr;
1967                 *q++ = curr;
1968                 break;
1969               default:
1970                 sprintf(q, "%d", runlength);
1971                 while (*q) q++;
1972                 *q++ = curr;
1973                 break;
1974             }
1975             runlength = 1;
1976             curr = *p;
1977         }
1978     } while (*p++);
1979     *q = NULLCHAR;
1980 }
1981
1982 /* Telnet protocol requests from the front end */
1983 void
1984 TelnetRequest(ddww, option)
1985      unsigned char ddww, option;
1986 {
1987     unsigned char msg[3];
1988     int outCount, outError;
1989
1990     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1991
1992     if (appData.debugMode) {
1993         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1994         switch (ddww) {
1995           case TN_DO:
1996             ddwwStr = "DO";
1997             break;
1998           case TN_DONT:
1999             ddwwStr = "DONT";
2000             break;
2001           case TN_WILL:
2002             ddwwStr = "WILL";
2003             break;
2004           case TN_WONT:
2005             ddwwStr = "WONT";
2006             break;
2007           default:
2008             ddwwStr = buf1;
2009             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2010             break;
2011         }
2012         switch (option) {
2013           case TN_ECHO:
2014             optionStr = "ECHO";
2015             break;
2016           default:
2017             optionStr = buf2;
2018             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2019             break;
2020         }
2021         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2022     }
2023     msg[0] = TN_IAC;
2024     msg[1] = ddww;
2025     msg[2] = option;
2026     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2027     if (outCount < 3) {
2028         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029     }
2030 }
2031
2032 void
2033 DoEcho()
2034 {
2035     if (!appData.icsActive) return;
2036     TelnetRequest(TN_DO, TN_ECHO);
2037 }
2038
2039 void
2040 DontEcho()
2041 {
2042     if (!appData.icsActive) return;
2043     TelnetRequest(TN_DONT, TN_ECHO);
2044 }
2045
2046 void
2047 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2048 {
2049     /* put the holdings sent to us by the server on the board holdings area */
2050     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2051     char p;
2052     ChessSquare piece;
2053
2054     if(gameInfo.holdingsWidth < 2)  return;
2055     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2056         return; // prevent overwriting by pre-board holdings
2057
2058     if( (int)lowestPiece >= BlackPawn ) {
2059         holdingsColumn = 0;
2060         countsColumn = 1;
2061         holdingsStartRow = BOARD_HEIGHT-1;
2062         direction = -1;
2063     } else {
2064         holdingsColumn = BOARD_WIDTH-1;
2065         countsColumn = BOARD_WIDTH-2;
2066         holdingsStartRow = 0;
2067         direction = 1;
2068     }
2069
2070     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2071         board[i][holdingsColumn] = EmptySquare;
2072         board[i][countsColumn]   = (ChessSquare) 0;
2073     }
2074     while( (p=*holdings++) != NULLCHAR ) {
2075         piece = CharToPiece( ToUpper(p) );
2076         if(piece == EmptySquare) continue;
2077         /*j = (int) piece - (int) WhitePawn;*/
2078         j = PieceToNumber(piece);
2079         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2080         if(j < 0) continue;               /* should not happen */
2081         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2082         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2083         board[holdingsStartRow+j*direction][countsColumn]++;
2084     }
2085 }
2086
2087
2088 void
2089 VariantSwitch(Board board, VariantClass newVariant)
2090 {
2091    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2092    static Board oldBoard;
2093
2094    startedFromPositionFile = FALSE;
2095    if(gameInfo.variant == newVariant) return;
2096
2097    /* [HGM] This routine is called each time an assignment is made to
2098     * gameInfo.variant during a game, to make sure the board sizes
2099     * are set to match the new variant. If that means adding or deleting
2100     * holdings, we shift the playing board accordingly
2101     * This kludge is needed because in ICS observe mode, we get boards
2102     * of an ongoing game without knowing the variant, and learn about the
2103     * latter only later. This can be because of the move list we requested,
2104     * in which case the game history is refilled from the beginning anyway,
2105     * but also when receiving holdings of a crazyhouse game. In the latter
2106     * case we want to add those holdings to the already received position.
2107     */
2108
2109
2110    if (appData.debugMode) {
2111      fprintf(debugFP, "Switch board from %s to %s\n",
2112              VariantName(gameInfo.variant), VariantName(newVariant));
2113      setbuf(debugFP, NULL);
2114    }
2115    shuffleOpenings = 0;       /* [HGM] shuffle */
2116    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2117    switch(newVariant)
2118      {
2119      case VariantShogi:
2120        newWidth = 9;  newHeight = 9;
2121        gameInfo.holdingsSize = 7;
2122      case VariantBughouse:
2123      case VariantCrazyhouse:
2124        newHoldingsWidth = 2; break;
2125      case VariantGreat:
2126        newWidth = 10;
2127      case VariantSuper:
2128        newHoldingsWidth = 2;
2129        gameInfo.holdingsSize = 8;
2130        break;
2131      case VariantGothic:
2132      case VariantCapablanca:
2133      case VariantCapaRandom:
2134        newWidth = 10;
2135      default:
2136        newHoldingsWidth = gameInfo.holdingsSize = 0;
2137      };
2138
2139    if(newWidth  != gameInfo.boardWidth  ||
2140       newHeight != gameInfo.boardHeight ||
2141       newHoldingsWidth != gameInfo.holdingsWidth ) {
2142
2143      /* shift position to new playing area, if needed */
2144      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2145        for(i=0; i<BOARD_HEIGHT; i++)
2146          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2147            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148              board[i][j];
2149        for(i=0; i<newHeight; i++) {
2150          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2151          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2152        }
2153      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2154        for(i=0; i<BOARD_HEIGHT; i++)
2155          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2156            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2157              board[i][j];
2158      }
2159      gameInfo.boardWidth  = newWidth;
2160      gameInfo.boardHeight = newHeight;
2161      gameInfo.holdingsWidth = newHoldingsWidth;
2162      gameInfo.variant = newVariant;
2163      InitDrawingSizes(-2, 0);
2164    } else gameInfo.variant = newVariant;
2165    CopyBoard(oldBoard, board);   // remember correctly formatted board
2166      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2167    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2168 }
2169
2170 static int loggedOn = FALSE;
2171
2172 /*-- Game start info cache: --*/
2173 int gs_gamenum;
2174 char gs_kind[MSG_SIZ];
2175 static char player1Name[128] = "";
2176 static char player2Name[128] = "";
2177 static char cont_seq[] = "\n\\   ";
2178 static int player1Rating = -1;
2179 static int player2Rating = -1;
2180 /*----------------------------*/
2181
2182 ColorClass curColor = ColorNormal;
2183 int suppressKibitz = 0;
2184
2185 // [HGM] seekgraph
2186 Boolean soughtPending = FALSE;
2187 Boolean seekGraphUp;
2188 #define MAX_SEEK_ADS 200
2189 #define SQUARE 0x80
2190 char *seekAdList[MAX_SEEK_ADS];
2191 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2192 float tcList[MAX_SEEK_ADS];
2193 char colorList[MAX_SEEK_ADS];
2194 int nrOfSeekAds = 0;
2195 int minRating = 1010, maxRating = 2800;
2196 int hMargin = 10, vMargin = 20, h, w;
2197 extern int squareSize, lineGap;
2198
2199 void
2200 PlotSeekAd(int i)
2201 {
2202         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2203         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2204         if(r < minRating+100 && r >=0 ) r = minRating+100;
2205         if(r > maxRating) r = maxRating;
2206         if(tc < 1.) tc = 1.;
2207         if(tc > 95.) tc = 95.;
2208         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2209         y = ((double)r - minRating)/(maxRating - minRating)
2210             * (h-vMargin-squareSize/8-1) + vMargin;
2211         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2212         if(strstr(seekAdList[i], " u ")) color = 1;
2213         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2214            !strstr(seekAdList[i], "bullet") &&
2215            !strstr(seekAdList[i], "blitz") &&
2216            !strstr(seekAdList[i], "standard") ) color = 2;
2217         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2218         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2219 }
2220
2221 void
2222 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2223 {
2224         char buf[MSG_SIZ], *ext = "";
2225         VariantClass v = StringToVariant(type);
2226         if(strstr(type, "wild")) {
2227             ext = type + 4; // append wild number
2228             if(v == VariantFischeRandom) type = "chess960"; else
2229             if(v == VariantLoadable) type = "setup"; else
2230             type = VariantName(v);
2231         }
2232         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2233         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2234             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2235             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2236             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2237             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2238             seekNrList[nrOfSeekAds] = nr;
2239             zList[nrOfSeekAds] = 0;
2240             seekAdList[nrOfSeekAds++] = StrSave(buf);
2241             if(plot) PlotSeekAd(nrOfSeekAds-1);
2242         }
2243 }
2244
2245 void
2246 EraseSeekDot(int i)
2247 {
2248     int x = xList[i], y = yList[i], d=squareSize/4, k;
2249     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2250     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2251     // now replot every dot that overlapped
2252     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2253         int xx = xList[k], yy = yList[k];
2254         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2255             DrawSeekDot(xx, yy, colorList[k]);
2256     }
2257 }
2258
2259 void
2260 RemoveSeekAd(int nr)
2261 {
2262         int i;
2263         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2264             EraseSeekDot(i);
2265             if(seekAdList[i]) free(seekAdList[i]);
2266             seekAdList[i] = seekAdList[--nrOfSeekAds];
2267             seekNrList[i] = seekNrList[nrOfSeekAds];
2268             ratingList[i] = ratingList[nrOfSeekAds];
2269             colorList[i]  = colorList[nrOfSeekAds];
2270             tcList[i] = tcList[nrOfSeekAds];
2271             xList[i]  = xList[nrOfSeekAds];
2272             yList[i]  = yList[nrOfSeekAds];
2273             zList[i]  = zList[nrOfSeekAds];
2274             seekAdList[nrOfSeekAds] = NULL;
2275             break;
2276         }
2277 }
2278
2279 Boolean
2280 MatchSoughtLine(char *line)
2281 {
2282     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2283     int nr, base, inc, u=0; char dummy;
2284
2285     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2286        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2287        (u=1) &&
2288        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2289         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2290         // match: compact and save the line
2291         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2292         return TRUE;
2293     }
2294     return FALSE;
2295 }
2296
2297 int
2298 DrawSeekGraph()
2299 {
2300     int i;
2301     if(!seekGraphUp) return FALSE;
2302     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2303     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2304
2305     DrawSeekBackground(0, 0, w, h);
2306     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2307     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2308     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2309         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2310         yy = h-1-yy;
2311         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2312         if(i%500 == 0) {
2313             char buf[MSG_SIZ];
2314             snprintf(buf, MSG_SIZ, "%d", i);
2315             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2316         }
2317     }
2318     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2319     for(i=1; i<100; i+=(i<10?1:5)) {
2320         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2321         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2322         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2323             char buf[MSG_SIZ];
2324             snprintf(buf, MSG_SIZ, "%d", i);
2325             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2326         }
2327     }
2328     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2329     return TRUE;
2330 }
2331
2332 int SeekGraphClick(ClickType click, int x, int y, int moving)
2333 {
2334     static int lastDown = 0, displayed = 0, lastSecond;
2335     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2336         if(click == Release || moving) return FALSE;
2337         nrOfSeekAds = 0;
2338         soughtPending = TRUE;
2339         SendToICS(ics_prefix);
2340         SendToICS("sought\n"); // should this be "sought all"?
2341     } else { // issue challenge based on clicked ad
2342         int dist = 10000; int i, closest = 0, second = 0;
2343         for(i=0; i<nrOfSeekAds; i++) {
2344             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2345             if(d < dist) { dist = d; closest = i; }
2346             second += (d - zList[i] < 120); // count in-range ads
2347             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2348         }
2349         if(dist < 120) {
2350             char buf[MSG_SIZ];
2351             second = (second > 1);
2352             if(displayed != closest || second != lastSecond) {
2353                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2354                 lastSecond = second; displayed = closest;
2355             }
2356             if(click == Press) {
2357                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2358                 lastDown = closest;
2359                 return TRUE;
2360             } // on press 'hit', only show info
2361             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2362             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2363             SendToICS(ics_prefix);
2364             SendToICS(buf);
2365             return TRUE; // let incoming board of started game pop down the graph
2366         } else if(click == Release) { // release 'miss' is ignored
2367             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2368             if(moving == 2) { // right up-click
2369                 nrOfSeekAds = 0; // refresh graph
2370                 soughtPending = TRUE;
2371                 SendToICS(ics_prefix);
2372                 SendToICS("sought\n"); // should this be "sought all"?
2373             }
2374             return TRUE;
2375         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2376         // press miss or release hit 'pop down' seek graph
2377         seekGraphUp = FALSE;
2378         DrawPosition(TRUE, NULL);
2379     }
2380     return TRUE;
2381 }
2382
2383 void
2384 read_from_ics(isr, closure, data, count, error)
2385      InputSourceRef isr;
2386      VOIDSTAR closure;
2387      char *data;
2388      int count;
2389      int error;
2390 {
2391 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2392 #define STARTED_NONE 0
2393 #define STARTED_MOVES 1
2394 #define STARTED_BOARD 2
2395 #define STARTED_OBSERVE 3
2396 #define STARTED_HOLDINGS 4
2397 #define STARTED_CHATTER 5
2398 #define STARTED_COMMENT 6
2399 #define STARTED_MOVES_NOHIDE 7
2400
2401     static int started = STARTED_NONE;
2402     static char parse[20000];
2403     static int parse_pos = 0;
2404     static char buf[BUF_SIZE + 1];
2405     static int firstTime = TRUE, intfSet = FALSE;
2406     static ColorClass prevColor = ColorNormal;
2407     static int savingComment = FALSE;
2408     static int cmatch = 0; // continuation sequence match
2409     char *bp;
2410     char str[MSG_SIZ];
2411     int i, oldi;
2412     int buf_len;
2413     int next_out;
2414     int tkind;
2415     int backup;    /* [DM] For zippy color lines */
2416     char *p;
2417     char talker[MSG_SIZ]; // [HGM] chat
2418     int channel;
2419
2420     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2421
2422     if (appData.debugMode) {
2423       if (!error) {
2424         fprintf(debugFP, "<ICS: ");
2425         show_bytes(debugFP, data, count);
2426         fprintf(debugFP, "\n");
2427       }
2428     }
2429
2430     if (appData.debugMode) { int f = forwardMostMove;
2431         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2432                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2433                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2434     }
2435     if (count > 0) {
2436         /* If last read ended with a partial line that we couldn't parse,
2437            prepend it to the new read and try again. */
2438         if (leftover_len > 0) {
2439             for (i=0; i<leftover_len; i++)
2440               buf[i] = buf[leftover_start + i];
2441         }
2442
2443     /* copy new characters into the buffer */
2444     bp = buf + leftover_len;
2445     buf_len=leftover_len;
2446     for (i=0; i<count; i++)
2447     {
2448         // ignore these
2449         if (data[i] == '\r')
2450             continue;
2451
2452         // join lines split by ICS?
2453         if (!appData.noJoin)
2454         {
2455             /*
2456                 Joining just consists of finding matches against the
2457                 continuation sequence, and discarding that sequence
2458                 if found instead of copying it.  So, until a match
2459                 fails, there's nothing to do since it might be the
2460                 complete sequence, and thus, something we don't want
2461                 copied.
2462             */
2463             if (data[i] == cont_seq[cmatch])
2464             {
2465                 cmatch++;
2466                 if (cmatch == strlen(cont_seq))
2467                 {
2468                     cmatch = 0; // complete match.  just reset the counter
2469
2470                     /*
2471                         it's possible for the ICS to not include the space
2472                         at the end of the last word, making our [correct]
2473                         join operation fuse two separate words.  the server
2474                         does this when the space occurs at the width setting.
2475                     */
2476                     if (!buf_len || buf[buf_len-1] != ' ')
2477                     {
2478                         *bp++ = ' ';
2479                         buf_len++;
2480                     }
2481                 }
2482                 continue;
2483             }
2484             else if (cmatch)
2485             {
2486                 /*
2487                     match failed, so we have to copy what matched before
2488                     falling through and copying this character.  In reality,
2489                     this will only ever be just the newline character, but
2490                     it doesn't hurt to be precise.
2491                 */
2492                 strncpy(bp, cont_seq, cmatch);
2493                 bp += cmatch;
2494                 buf_len += cmatch;
2495                 cmatch = 0;
2496             }
2497         }
2498
2499         // copy this char
2500         *bp++ = data[i];
2501         buf_len++;
2502     }
2503
2504         buf[buf_len] = NULLCHAR;
2505 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2506         next_out = 0;
2507         leftover_start = 0;
2508
2509         i = 0;
2510         while (i < buf_len) {
2511             /* Deal with part of the TELNET option negotiation
2512                protocol.  We refuse to do anything beyond the
2513                defaults, except that we allow the WILL ECHO option,
2514                which ICS uses to turn off password echoing when we are
2515                directly connected to it.  We reject this option
2516                if localLineEditing mode is on (always on in xboard)
2517                and we are talking to port 23, which might be a real
2518                telnet server that will try to keep WILL ECHO on permanently.
2519              */
2520             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2521                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2522                 unsigned char option;
2523                 oldi = i;
2524                 switch ((unsigned char) buf[++i]) {
2525                   case TN_WILL:
2526                     if (appData.debugMode)
2527                       fprintf(debugFP, "\n<WILL ");
2528                     switch (option = (unsigned char) buf[++i]) {
2529                       case TN_ECHO:
2530                         if (appData.debugMode)
2531                           fprintf(debugFP, "ECHO ");
2532                         /* Reply only if this is a change, according
2533                            to the protocol rules. */
2534                         if (remoteEchoOption) break;
2535                         if (appData.localLineEditing &&
2536                             atoi(appData.icsPort) == TN_PORT) {
2537                             TelnetRequest(TN_DONT, TN_ECHO);
2538                         } else {
2539                             EchoOff();
2540                             TelnetRequest(TN_DO, TN_ECHO);
2541                             remoteEchoOption = TRUE;
2542                         }
2543                         break;
2544                       default:
2545                         if (appData.debugMode)
2546                           fprintf(debugFP, "%d ", option);
2547                         /* Whatever this is, we don't want it. */
2548                         TelnetRequest(TN_DONT, option);
2549                         break;
2550                     }
2551                     break;
2552                   case TN_WONT:
2553                     if (appData.debugMode)
2554                       fprintf(debugFP, "\n<WONT ");
2555                     switch (option = (unsigned char) buf[++i]) {
2556                       case TN_ECHO:
2557                         if (appData.debugMode)
2558                           fprintf(debugFP, "ECHO ");
2559                         /* Reply only if this is a change, according
2560                            to the protocol rules. */
2561                         if (!remoteEchoOption) break;
2562                         EchoOn();
2563                         TelnetRequest(TN_DONT, TN_ECHO);
2564                         remoteEchoOption = FALSE;
2565                         break;
2566                       default:
2567                         if (appData.debugMode)
2568                           fprintf(debugFP, "%d ", (unsigned char) option);
2569                         /* Whatever this is, it must already be turned
2570                            off, because we never agree to turn on
2571                            anything non-default, so according to the
2572                            protocol rules, we don't reply. */
2573                         break;
2574                     }
2575                     break;
2576                   case TN_DO:
2577                     if (appData.debugMode)
2578                       fprintf(debugFP, "\n<DO ");
2579                     switch (option = (unsigned char) buf[++i]) {
2580                       default:
2581                         /* Whatever this is, we refuse to do it. */
2582                         if (appData.debugMode)
2583                           fprintf(debugFP, "%d ", option);
2584                         TelnetRequest(TN_WONT, option);
2585                         break;
2586                     }
2587                     break;
2588                   case TN_DONT:
2589                     if (appData.debugMode)
2590                       fprintf(debugFP, "\n<DONT ");
2591                     switch (option = (unsigned char) buf[++i]) {
2592                       default:
2593                         if (appData.debugMode)
2594                           fprintf(debugFP, "%d ", option);
2595                         /* Whatever this is, we are already not doing
2596                            it, because we never agree to do anything
2597                            non-default, so according to the protocol
2598                            rules, we don't reply. */
2599                         break;
2600                     }
2601                     break;
2602                   case TN_IAC:
2603                     if (appData.debugMode)
2604                       fprintf(debugFP, "\n<IAC ");
2605                     /* Doubled IAC; pass it through */
2606                     i--;
2607                     break;
2608                   default:
2609                     if (appData.debugMode)
2610                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2611                     /* Drop all other telnet commands on the floor */
2612                     break;
2613                 }
2614                 if (oldi > next_out)
2615                   SendToPlayer(&buf[next_out], oldi - next_out);
2616                 if (++i > next_out)
2617                   next_out = i;
2618                 continue;
2619             }
2620
2621             /* OK, this at least will *usually* work */
2622             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2623                 loggedOn = TRUE;
2624             }
2625
2626             if (loggedOn && !intfSet) {
2627                 if (ics_type == ICS_ICC) {
2628                   snprintf(str, MSG_SIZ,
2629                           "/set-quietly interface %s\n/set-quietly style 12\n",
2630                           programVersion);
2631                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2632                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2633                 } else if (ics_type == ICS_CHESSNET) {
2634                   snprintf(str, MSG_SIZ, "/style 12\n");
2635                 } else {
2636                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2637                   strcat(str, programVersion);
2638                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2639                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2640                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2641 #ifdef WIN32
2642                   strcat(str, "$iset nohighlight 1\n");
2643 #endif
2644                   strcat(str, "$iset lock 1\n$style 12\n");
2645                 }
2646                 SendToICS(str);
2647                 NotifyFrontendLogin();
2648                 intfSet = TRUE;
2649             }
2650
2651             if (started == STARTED_COMMENT) {
2652                 /* Accumulate characters in comment */
2653                 parse[parse_pos++] = buf[i];
2654                 if (buf[i] == '\n') {
2655                     parse[parse_pos] = NULLCHAR;
2656                     if(chattingPartner>=0) {
2657                         char mess[MSG_SIZ];
2658                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2659                         OutputChatMessage(chattingPartner, mess);
2660                         chattingPartner = -1;
2661                         next_out = i+1; // [HGM] suppress printing in ICS window
2662                     } else
2663                     if(!suppressKibitz) // [HGM] kibitz
2664                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2665                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2666                         int nrDigit = 0, nrAlph = 0, j;
2667                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2668                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2669                         parse[parse_pos] = NULLCHAR;
2670                         // try to be smart: if it does not look like search info, it should go to
2671                         // ICS interaction window after all, not to engine-output window.
2672                         for(j=0; j<parse_pos; j++) { // count letters and digits
2673                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2674                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2675                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2676                         }
2677                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2678                             int depth=0; float score;
2679                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2680                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2681                                 pvInfoList[forwardMostMove-1].depth = depth;
2682                                 pvInfoList[forwardMostMove-1].score = 100*score;
2683                             }
2684                             OutputKibitz(suppressKibitz, parse);
2685                         } else {
2686                             char tmp[MSG_SIZ];
2687                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2688                             SendToPlayer(tmp, strlen(tmp));
2689                         }
2690                         next_out = i+1; // [HGM] suppress printing in ICS window
2691                     }
2692                     started = STARTED_NONE;
2693                 } else {
2694                     /* Don't match patterns against characters in comment */
2695                     i++;
2696                     continue;
2697                 }
2698             }
2699             if (started == STARTED_CHATTER) {
2700                 if (buf[i] != '\n') {
2701                     /* Don't match patterns against characters in chatter */
2702                     i++;
2703                     continue;
2704                 }
2705                 started = STARTED_NONE;
2706                 if(suppressKibitz) next_out = i+1;
2707             }
2708
2709             /* Kludge to deal with rcmd protocol */
2710             if (firstTime && looking_at(buf, &i, "\001*")) {
2711                 DisplayFatalError(&buf[1], 0, 1);
2712                 continue;
2713             } else {
2714                 firstTime = FALSE;
2715             }
2716
2717             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2718                 ics_type = ICS_ICC;
2719                 ics_prefix = "/";
2720                 if (appData.debugMode)
2721                   fprintf(debugFP, "ics_type %d\n", ics_type);
2722                 continue;
2723             }
2724             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2725                 ics_type = ICS_FICS;
2726                 ics_prefix = "$";
2727                 if (appData.debugMode)
2728                   fprintf(debugFP, "ics_type %d\n", ics_type);
2729                 continue;
2730             }
2731             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2732                 ics_type = ICS_CHESSNET;
2733                 ics_prefix = "/";
2734                 if (appData.debugMode)
2735                   fprintf(debugFP, "ics_type %d\n", ics_type);
2736                 continue;
2737             }
2738
2739             if (!loggedOn &&
2740                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2741                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2742                  looking_at(buf, &i, "will be \"*\""))) {
2743               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2744               continue;
2745             }
2746
2747             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2748               char buf[MSG_SIZ];
2749               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2750               DisplayIcsInteractionTitle(buf);
2751               have_set_title = TRUE;
2752             }
2753
2754             /* skip finger notes */
2755             if (started == STARTED_NONE &&
2756                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2757                  (buf[i] == '1' && buf[i+1] == '0')) &&
2758                 buf[i+2] == ':' && buf[i+3] == ' ') {
2759               started = STARTED_CHATTER;
2760               i += 3;
2761               continue;
2762             }
2763
2764             oldi = i;
2765             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2766             if(appData.seekGraph) {
2767                 if(soughtPending && MatchSoughtLine(buf+i)) {
2768                     i = strstr(buf+i, "rated") - buf;
2769                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2770                     next_out = leftover_start = i;
2771                     started = STARTED_CHATTER;
2772                     suppressKibitz = TRUE;
2773                     continue;
2774                 }
2775                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2776                         && looking_at(buf, &i, "* ads displayed")) {
2777                     soughtPending = FALSE;
2778                     seekGraphUp = TRUE;
2779                     DrawSeekGraph();
2780                     continue;
2781                 }
2782                 if(appData.autoRefresh) {
2783                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2784                         int s = (ics_type == ICS_ICC); // ICC format differs
2785                         if(seekGraphUp)
2786                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2787                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2788                         looking_at(buf, &i, "*% "); // eat prompt
2789                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2790                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791                         next_out = i; // suppress
2792                         continue;
2793                     }
2794                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2795                         char *p = star_match[0];
2796                         while(*p) {
2797                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2798                             while(*p && *p++ != ' '); // next
2799                         }
2800                         looking_at(buf, &i, "*% "); // eat prompt
2801                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2802                         next_out = i;
2803                         continue;
2804                     }
2805                 }
2806             }
2807
2808             /* skip formula vars */
2809             if (started == STARTED_NONE &&
2810                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2811               started = STARTED_CHATTER;
2812               i += 3;
2813               continue;
2814             }
2815
2816             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2817             if (appData.autoKibitz && started == STARTED_NONE &&
2818                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2819                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2820                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2821                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2822                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2823                         suppressKibitz = TRUE;
2824                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2825                         next_out = i;
2826                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2827                                 && (gameMode == IcsPlayingWhite)) ||
2828                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2829                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2830                             started = STARTED_CHATTER; // own kibitz we simply discard
2831                         else {
2832                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2833                             parse_pos = 0; parse[0] = NULLCHAR;
2834                             savingComment = TRUE;
2835                             suppressKibitz = gameMode != IcsObserving ? 2 :
2836                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2837                         }
2838                         continue;
2839                 } else
2840                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2841                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2842                          && atoi(star_match[0])) {
2843                     // suppress the acknowledgements of our own autoKibitz
2844                     char *p;
2845                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2846                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2847                     SendToPlayer(star_match[0], strlen(star_match[0]));
2848                     if(looking_at(buf, &i, "*% ")) // eat prompt
2849                         suppressKibitz = FALSE;
2850                     next_out = i;
2851                     continue;
2852                 }
2853             } // [HGM] kibitz: end of patch
2854
2855             // [HGM] chat: intercept tells by users for which we have an open chat window
2856             channel = -1;
2857             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2858                                            looking_at(buf, &i, "* whispers:") ||
2859                                            looking_at(buf, &i, "* kibitzes:") ||
2860                                            looking_at(buf, &i, "* shouts:") ||
2861                                            looking_at(buf, &i, "* c-shouts:") ||
2862                                            looking_at(buf, &i, "--> * ") ||
2863                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2864                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2865                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2866                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2867                 int p;
2868                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2869                 chattingPartner = -1;
2870
2871                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2872                 for(p=0; p<MAX_CHAT; p++) {
2873                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2874                     talker[0] = '['; strcat(talker, "] ");
2875                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2876                     chattingPartner = p; break;
2877                     }
2878                 } else
2879                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2880                 for(p=0; p<MAX_CHAT; p++) {
2881                     if(!strcmp("kibitzes", chatPartner[p])) {
2882                         talker[0] = '['; strcat(talker, "] ");
2883                         chattingPartner = p; break;
2884                     }
2885                 } else
2886                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2887                 for(p=0; p<MAX_CHAT; p++) {
2888                     if(!strcmp("whispers", chatPartner[p])) {
2889                         talker[0] = '['; strcat(talker, "] ");
2890                         chattingPartner = p; break;
2891                     }
2892                 } else
2893                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2894                   if(buf[i-8] == '-' && buf[i-3] == 't')
2895                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2896                     if(!strcmp("c-shouts", chatPartner[p])) {
2897                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                   if(chattingPartner < 0)
2902                   for(p=0; p<MAX_CHAT; p++) {
2903                     if(!strcmp("shouts", chatPartner[p])) {
2904                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2905                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2906                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2907                         chattingPartner = p; break;
2908                     }
2909                   }
2910                 }
2911                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2912                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2913                     talker[0] = 0; Colorize(ColorTell, FALSE);
2914                     chattingPartner = p; break;
2915                 }
2916                 if(chattingPartner<0) i = oldi; else {
2917                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2918                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2919                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2920                     started = STARTED_COMMENT;
2921                     parse_pos = 0; parse[0] = NULLCHAR;
2922                     savingComment = 3 + chattingPartner; // counts as TRUE
2923                     suppressKibitz = TRUE;
2924                     continue;
2925                 }
2926             } // [HGM] chat: end of patch
2927
2928             if (appData.zippyTalk || appData.zippyPlay) {
2929                 /* [DM] Backup address for color zippy lines */
2930                 backup = i;
2931 #if ZIPPY
2932                if (loggedOn == TRUE)
2933                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2934                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2935 #endif
2936             } // [DM] 'else { ' deleted
2937                 if (
2938                     /* Regular tells and says */
2939                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2940                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2941                     looking_at(buf, &i, "* says: ") ||
2942                     /* Don't color "message" or "messages" output */
2943                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2944                     looking_at(buf, &i, "*. * at *:*: ") ||
2945                     looking_at(buf, &i, "--* (*:*): ") ||
2946                     /* Message notifications (same color as tells) */
2947                     looking_at(buf, &i, "* has left a message ") ||
2948                     looking_at(buf, &i, "* just sent you a message:\n") ||
2949                     /* Whispers and kibitzes */
2950                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2951                     looking_at(buf, &i, "* kibitzes: ") ||
2952                     /* Channel tells */
2953                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2954
2955                   if (tkind == 1 && strchr(star_match[0], ':')) {
2956                       /* Avoid "tells you:" spoofs in channels */
2957                      tkind = 3;
2958                   }
2959                   if (star_match[0][0] == NULLCHAR ||
2960                       strchr(star_match[0], ' ') ||
2961                       (tkind == 3 && strchr(star_match[1], ' '))) {
2962                     /* Reject bogus matches */
2963                     i = oldi;
2964                   } else {
2965                     if (appData.colorize) {
2966                       if (oldi > next_out) {
2967                         SendToPlayer(&buf[next_out], oldi - next_out);
2968                         next_out = oldi;
2969                       }
2970                       switch (tkind) {
2971                       case 1:
2972                         Colorize(ColorTell, FALSE);
2973                         curColor = ColorTell;
2974                         break;
2975                       case 2:
2976                         Colorize(ColorKibitz, FALSE);
2977                         curColor = ColorKibitz;
2978                         break;
2979                       case 3:
2980                         p = strrchr(star_match[1], '(');
2981                         if (p == NULL) {
2982                           p = star_match[1];
2983                         } else {
2984                           p++;
2985                         }
2986                         if (atoi(p) == 1) {
2987                           Colorize(ColorChannel1, FALSE);
2988                           curColor = ColorChannel1;
2989                         } else {
2990                           Colorize(ColorChannel, FALSE);
2991                           curColor = ColorChannel;
2992                         }
2993                         break;
2994                       case 5:
2995                         curColor = ColorNormal;
2996                         break;
2997                       }
2998                     }
2999                     if (started == STARTED_NONE && appData.autoComment &&
3000                         (gameMode == IcsObserving ||
3001                          gameMode == IcsPlayingWhite ||
3002                          gameMode == IcsPlayingBlack)) {
3003                       parse_pos = i - oldi;
3004                       memcpy(parse, &buf[oldi], parse_pos);
3005                       parse[parse_pos] = NULLCHAR;
3006                       started = STARTED_COMMENT;
3007                       savingComment = TRUE;
3008                     } else {
3009                       started = STARTED_CHATTER;
3010                       savingComment = FALSE;
3011                     }
3012                     loggedOn = TRUE;
3013                     continue;
3014                   }
3015                 }
3016
3017                 if (looking_at(buf, &i, "* s-shouts: ") ||
3018                     looking_at(buf, &i, "* c-shouts: ")) {
3019                     if (appData.colorize) {
3020                         if (oldi > next_out) {
3021                             SendToPlayer(&buf[next_out], oldi - next_out);
3022                             next_out = oldi;
3023                         }
3024                         Colorize(ColorSShout, FALSE);
3025                         curColor = ColorSShout;
3026                     }
3027                     loggedOn = TRUE;
3028                     started = STARTED_CHATTER;
3029                     continue;
3030                 }
3031
3032                 if (looking_at(buf, &i, "--->")) {
3033                     loggedOn = TRUE;
3034                     continue;
3035                 }
3036
3037                 if (looking_at(buf, &i, "* shouts: ") ||
3038                     looking_at(buf, &i, "--> ")) {
3039                     if (appData.colorize) {
3040                         if (oldi > next_out) {
3041                             SendToPlayer(&buf[next_out], oldi - next_out);
3042                             next_out = oldi;
3043                         }
3044                         Colorize(ColorShout, FALSE);
3045                         curColor = ColorShout;
3046                     }
3047                     loggedOn = TRUE;
3048                     started = STARTED_CHATTER;
3049                     continue;
3050                 }
3051
3052                 if (looking_at( buf, &i, "Challenge:")) {
3053                     if (appData.colorize) {
3054                         if (oldi > next_out) {
3055                             SendToPlayer(&buf[next_out], oldi - next_out);
3056                             next_out = oldi;
3057                         }
3058                         Colorize(ColorChallenge, FALSE);
3059                         curColor = ColorChallenge;
3060                     }
3061                     loggedOn = TRUE;
3062                     continue;
3063                 }
3064
3065                 if (looking_at(buf, &i, "* offers you") ||
3066                     looking_at(buf, &i, "* offers to be") ||
3067                     looking_at(buf, &i, "* would like to") ||
3068                     looking_at(buf, &i, "* requests to") ||
3069                     looking_at(buf, &i, "Your opponent offers") ||
3070                     looking_at(buf, &i, "Your opponent requests")) {
3071
3072                     if (appData.colorize) {
3073                         if (oldi > next_out) {
3074                             SendToPlayer(&buf[next_out], oldi - next_out);
3075                             next_out = oldi;
3076                         }
3077                         Colorize(ColorRequest, FALSE);
3078                         curColor = ColorRequest;
3079                     }
3080                     continue;
3081                 }
3082
3083                 if (looking_at(buf, &i, "* (*) seeking")) {
3084                     if (appData.colorize) {
3085                         if (oldi > next_out) {
3086                             SendToPlayer(&buf[next_out], oldi - next_out);
3087                             next_out = oldi;
3088                         }
3089                         Colorize(ColorSeek, FALSE);
3090                         curColor = ColorSeek;
3091                     }
3092                     continue;
3093             }
3094
3095             if (looking_at(buf, &i, "\\   ")) {
3096                 if (prevColor != ColorNormal) {
3097                     if (oldi > next_out) {
3098                         SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = oldi;
3100                     }
3101                     Colorize(prevColor, TRUE);
3102                     curColor = prevColor;
3103                 }
3104                 if (savingComment) {
3105                     parse_pos = i - oldi;
3106                     memcpy(parse, &buf[oldi], parse_pos);
3107                     parse[parse_pos] = NULLCHAR;
3108                     started = STARTED_COMMENT;
3109                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3110                         chattingPartner = savingComment - 3; // kludge to remember the box
3111                 } else {
3112                     started = STARTED_CHATTER;
3113                 }
3114                 continue;
3115             }
3116
3117             if (looking_at(buf, &i, "Black Strength :") ||
3118                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3119                 looking_at(buf, &i, "<10>") ||
3120                 looking_at(buf, &i, "#@#")) {
3121                 /* Wrong board style */
3122                 loggedOn = TRUE;
3123                 SendToICS(ics_prefix);
3124                 SendToICS("set style 12\n");
3125                 SendToICS(ics_prefix);
3126                 SendToICS("refresh\n");
3127                 continue;
3128             }
3129
3130             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3131                 ICSInitScript();
3132                 have_sent_ICS_logon = 1;
3133                 continue;
3134             }
3135
3136             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3137                 (looking_at(buf, &i, "\n<12> ") ||
3138                  looking_at(buf, &i, "<12> "))) {
3139                 loggedOn = TRUE;
3140                 if (oldi > next_out) {
3141                     SendToPlayer(&buf[next_out], oldi - next_out);
3142                 }
3143                 next_out = i;
3144                 started = STARTED_BOARD;
3145                 parse_pos = 0;
3146                 continue;
3147             }
3148
3149             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3150                 looking_at(buf, &i, "<b1> ")) {
3151                 if (oldi > next_out) {
3152                     SendToPlayer(&buf[next_out], oldi - next_out);
3153                 }
3154                 next_out = i;
3155                 started = STARTED_HOLDINGS;
3156                 parse_pos = 0;
3157                 continue;
3158             }
3159
3160             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3161                 loggedOn = TRUE;
3162                 /* Header for a move list -- first line */
3163
3164                 switch (ics_getting_history) {
3165                   case H_FALSE:
3166                     switch (gameMode) {
3167                       case IcsIdle:
3168                       case BeginningOfGame:
3169                         /* User typed "moves" or "oldmoves" while we
3170                            were idle.  Pretend we asked for these
3171                            moves and soak them up so user can step
3172                            through them and/or save them.
3173                            */
3174                         Reset(FALSE, TRUE);
3175                         gameMode = IcsObserving;
3176                         ModeHighlight();
3177                         ics_gamenum = -1;
3178                         ics_getting_history = H_GOT_UNREQ_HEADER;
3179                         break;
3180                       case EditGame: /*?*/
3181                       case EditPosition: /*?*/
3182                         /* Should above feature work in these modes too? */
3183                         /* For now it doesn't */
3184                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3185                         break;
3186                       default:
3187                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3188                         break;
3189                     }
3190                     break;
3191                   case H_REQUESTED:
3192                     /* Is this the right one? */
3193                     if (gameInfo.white && gameInfo.black &&
3194                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3195                         strcmp(gameInfo.black, star_match[2]) == 0) {
3196                         /* All is well */
3197                         ics_getting_history = H_GOT_REQ_HEADER;
3198                     }
3199                     break;
3200                   case H_GOT_REQ_HEADER:
3201                   case H_GOT_UNREQ_HEADER:
3202                   case H_GOT_UNWANTED_HEADER:
3203                   case H_GETTING_MOVES:
3204                     /* Should not happen */
3205                     DisplayError(_("Error gathering move list: two headers"), 0);
3206                     ics_getting_history = H_FALSE;
3207                     break;
3208                 }
3209
3210                 /* Save player ratings into gameInfo if needed */
3211                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3212                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3213                     (gameInfo.whiteRating == -1 ||
3214                      gameInfo.blackRating == -1)) {
3215
3216                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3217                     gameInfo.blackRating = string_to_rating(star_match[3]);
3218                     if (appData.debugMode)
3219                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3220                               gameInfo.whiteRating, gameInfo.blackRating);
3221                 }
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i,
3226               "* * match, initial time: * minute*, increment: * second")) {
3227                 /* Header for a move list -- second line */
3228                 /* Initial board will follow if this is a wild game */
3229                 if (gameInfo.event != NULL) free(gameInfo.event);
3230                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3231                 gameInfo.event = StrSave(str);
3232                 /* [HGM] we switched variant. Translate boards if needed. */
3233                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3234                 continue;
3235             }
3236
3237             if (looking_at(buf, &i, "Move  ")) {
3238                 /* Beginning of a move list */
3239                 switch (ics_getting_history) {
3240                   case H_FALSE:
3241                     /* Normally should not happen */
3242                     /* Maybe user hit reset while we were parsing */
3243                     break;
3244                   case H_REQUESTED:
3245                     /* Happens if we are ignoring a move list that is not
3246                      * the one we just requested.  Common if the user
3247                      * tries to observe two games without turning off
3248                      * getMoveList */
3249                     break;
3250                   case H_GETTING_MOVES:
3251                     /* Should not happen */
3252                     DisplayError(_("Error gathering move list: nested"), 0);
3253                     ics_getting_history = H_FALSE;
3254                     break;
3255                   case H_GOT_REQ_HEADER:
3256                     ics_getting_history = H_GETTING_MOVES;
3257                     started = STARTED_MOVES;
3258                     parse_pos = 0;
3259                     if (oldi > next_out) {
3260                         SendToPlayer(&buf[next_out], oldi - next_out);
3261                     }
3262                     break;
3263                   case H_GOT_UNREQ_HEADER:
3264                     ics_getting_history = H_GETTING_MOVES;
3265                     started = STARTED_MOVES_NOHIDE;
3266                     parse_pos = 0;
3267                     break;
3268                   case H_GOT_UNWANTED_HEADER:
3269                     ics_getting_history = H_FALSE;
3270                     break;
3271                 }
3272                 continue;
3273             }
3274
3275             if (looking_at(buf, &i, "% ") ||
3276                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3277                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3278                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3279                     soughtPending = FALSE;
3280                     seekGraphUp = TRUE;
3281                     DrawSeekGraph();
3282                 }
3283                 if(suppressKibitz) next_out = i;
3284                 savingComment = FALSE;
3285                 suppressKibitz = 0;
3286                 switch (started) {
3287                   case STARTED_MOVES:
3288                   case STARTED_MOVES_NOHIDE:
3289                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3290                     parse[parse_pos + i - oldi] = NULLCHAR;
3291                     ParseGameHistory(parse);
3292 #if ZIPPY
3293                     if (appData.zippyPlay && first.initDone) {
3294                         FeedMovesToProgram(&first, forwardMostMove);
3295                         if (gameMode == IcsPlayingWhite) {
3296                             if (WhiteOnMove(forwardMostMove)) {
3297                                 if (first.sendTime) {
3298                                   if (first.useColors) {
3299                                     SendToProgram("black\n", &first);
3300                                   }
3301                                   SendTimeRemaining(&first, TRUE);
3302                                 }
3303                                 if (first.useColors) {
3304                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3305                                 }
3306                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3307                                 first.maybeThinking = TRUE;
3308                             } else {
3309                                 if (first.usePlayother) {
3310                                   if (first.sendTime) {
3311                                     SendTimeRemaining(&first, TRUE);
3312                                   }
3313                                   SendToProgram("playother\n", &first);
3314                                   firstMove = FALSE;
3315                                 } else {
3316                                   firstMove = TRUE;
3317                                 }
3318                             }
3319                         } else if (gameMode == IcsPlayingBlack) {
3320                             if (!WhiteOnMove(forwardMostMove)) {
3321                                 if (first.sendTime) {
3322                                   if (first.useColors) {
3323                                     SendToProgram("white\n", &first);
3324                                   }
3325                                   SendTimeRemaining(&first, FALSE);
3326                                 }
3327                                 if (first.useColors) {
3328                                   SendToProgram("black\n", &first);
3329                                 }
3330                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3331                                 first.maybeThinking = TRUE;
3332                             } else {
3333                                 if (first.usePlayother) {
3334                                   if (first.sendTime) {
3335                                     SendTimeRemaining(&first, FALSE);
3336                                   }
3337                                   SendToProgram("playother\n", &first);
3338                                   firstMove = FALSE;
3339                                 } else {
3340                                   firstMove = TRUE;
3341                                 }
3342                             }
3343                         }
3344                     }
3345 #endif
3346                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3347                         /* Moves came from oldmoves or moves command
3348                            while we weren't doing anything else.
3349                            */
3350                         currentMove = forwardMostMove;
3351                         ClearHighlights();/*!!could figure this out*/
3352                         flipView = appData.flipView;
3353                         DrawPosition(TRUE, boards[currentMove]);
3354                         DisplayBothClocks();
3355                         snprintf(str, MSG_SIZ, "%s vs. %s",
3356                                 gameInfo.white, gameInfo.black);
3357                         DisplayTitle(str);
3358                         gameMode = IcsIdle;
3359                     } else {
3360                         /* Moves were history of an active game */
3361                         if (gameInfo.resultDetails != NULL) {
3362                             free(gameInfo.resultDetails);
3363                             gameInfo.resultDetails = NULL;
3364                         }
3365                     }
3366                     HistorySet(parseList, backwardMostMove,
3367                                forwardMostMove, currentMove-1);
3368                     DisplayMove(currentMove - 1);
3369                     if (started == STARTED_MOVES) next_out = i;
3370                     started = STARTED_NONE;
3371                     ics_getting_history = H_FALSE;
3372                     break;
3373
3374                   case STARTED_OBSERVE:
3375                     started = STARTED_NONE;
3376                     SendToICS(ics_prefix);
3377                     SendToICS("refresh\n");
3378                     break;
3379
3380                   default:
3381                     break;
3382                 }
3383                 if(bookHit) { // [HGM] book: simulate book reply
3384                     static char bookMove[MSG_SIZ]; // a bit generous?
3385
3386                     programStats.nodes = programStats.depth = programStats.time =
3387                     programStats.score = programStats.got_only_move = 0;
3388                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3389
3390                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3391                     strcat(bookMove, bookHit);
3392                     HandleMachineMove(bookMove, &first);
3393                 }
3394                 continue;
3395             }
3396
3397             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3398                  started == STARTED_HOLDINGS ||
3399                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3400                 /* Accumulate characters in move list or board */
3401                 parse[parse_pos++] = buf[i];
3402             }
3403
3404             /* Start of game messages.  Mostly we detect start of game
3405                when the first board image arrives.  On some versions
3406                of the ICS, though, we need to do a "refresh" after starting
3407                to observe in order to get the current board right away. */
3408             if (looking_at(buf, &i, "Adding game * to observation list")) {
3409                 started = STARTED_OBSERVE;
3410                 continue;
3411             }
3412
3413             /* Handle auto-observe */
3414             if (appData.autoObserve &&
3415                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3416                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3417                 char *player;
3418                 /* Choose the player that was highlighted, if any. */
3419                 if (star_match[0][0] == '\033' ||
3420                     star_match[1][0] != '\033') {
3421                     player = star_match[0];
3422                 } else {
3423                     player = star_match[2];
3424                 }
3425                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3426                         ics_prefix, StripHighlightAndTitle(player));
3427                 SendToICS(str);
3428
3429                 /* Save ratings from notify string */
3430                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3431                 player1Rating = string_to_rating(star_match[1]);
3432                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3433                 player2Rating = string_to_rating(star_match[3]);
3434
3435                 if (appData.debugMode)
3436                   fprintf(debugFP,
3437                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3438                           player1Name, player1Rating,
3439                           player2Name, player2Rating);
3440
3441                 continue;
3442             }
3443
3444             /* Deal with automatic examine mode after a game,
3445                and with IcsObserving -> IcsExamining transition */
3446             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3447                 looking_at(buf, &i, "has made you an examiner of game *")) {
3448
3449                 int gamenum = atoi(star_match[0]);
3450                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3451                     gamenum == ics_gamenum) {
3452                     /* We were already playing or observing this game;
3453                        no need to refetch history */
3454                     gameMode = IcsExamining;
3455                     if (pausing) {
3456                         pauseExamForwardMostMove = forwardMostMove;
3457                     } else if (currentMove < forwardMostMove) {
3458                         ForwardInner(forwardMostMove);
3459                     }
3460                 } else {
3461                     /* I don't think this case really can happen */
3462                     SendToICS(ics_prefix);
3463                     SendToICS("refresh\n");
3464                 }
3465                 continue;
3466             }
3467
3468             /* Error messages */
3469 //          if (ics_user_moved) {
3470             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3471                 if (looking_at(buf, &i, "Illegal move") ||
3472                     looking_at(buf, &i, "Not a legal move") ||
3473                     looking_at(buf, &i, "Your king is in check") ||
3474                     looking_at(buf, &i, "It isn't your turn") ||
3475                     looking_at(buf, &i, "It is not your move")) {
3476                     /* Illegal move */
3477                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3478                         currentMove = forwardMostMove-1;
3479                         DisplayMove(currentMove - 1); /* before DMError */
3480                         DrawPosition(FALSE, boards[currentMove]);
3481                         SwitchClocks(forwardMostMove-1); // [HGM] race
3482                         DisplayBothClocks();
3483                     }
3484                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3485                     ics_user_moved = 0;
3486                     continue;
3487                 }
3488             }
3489
3490             if (looking_at(buf, &i, "still have time") ||
3491                 looking_at(buf, &i, "not out of time") ||
3492                 looking_at(buf, &i, "either player is out of time") ||
3493                 looking_at(buf, &i, "has timeseal; checking")) {
3494                 /* We must have called his flag a little too soon */
3495                 whiteFlag = blackFlag = FALSE;
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i, "added * seconds to") ||
3500                 looking_at(buf, &i, "seconds were added to")) {
3501                 /* Update the clocks */
3502                 SendToICS(ics_prefix);
3503                 SendToICS("refresh\n");
3504                 continue;
3505             }
3506
3507             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3508                 ics_clock_paused = TRUE;
3509                 StopClocks();
3510                 continue;
3511             }
3512
3513             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3514                 ics_clock_paused = FALSE;
3515                 StartClocks();
3516                 continue;
3517             }
3518
3519             /* Grab player ratings from the Creating: message.
3520                Note we have to check for the special case when
3521                the ICS inserts things like [white] or [black]. */
3522             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3523                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3524                 /* star_matches:
3525                    0    player 1 name (not necessarily white)
3526                    1    player 1 rating
3527                    2    empty, white, or black (IGNORED)
3528                    3    player 2 name (not necessarily black)
3529                    4    player 2 rating
3530
3531                    The names/ratings are sorted out when the game
3532                    actually starts (below).
3533                 */
3534                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3535                 player1Rating = string_to_rating(star_match[1]);
3536                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3537                 player2Rating = string_to_rating(star_match[4]);
3538
3539                 if (appData.debugMode)
3540                   fprintf(debugFP,
3541                           "Ratings from 'Creating:' %s %d, %s %d\n",
3542                           player1Name, player1Rating,
3543                           player2Name, player2Rating);
3544
3545                 continue;
3546             }
3547
3548             /* Improved generic start/end-of-game messages */
3549             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3550                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3551                 /* If tkind == 0: */
3552                 /* star_match[0] is the game number */
3553                 /*           [1] is the white player's name */
3554                 /*           [2] is the black player's name */
3555                 /* For end-of-game: */
3556                 /*           [3] is the reason for the game end */
3557                 /*           [4] is a PGN end game-token, preceded by " " */
3558                 /* For start-of-game: */
3559                 /*           [3] begins with "Creating" or "Continuing" */
3560                 /*           [4] is " *" or empty (don't care). */
3561                 int gamenum = atoi(star_match[0]);
3562                 char *whitename, *blackname, *why, *endtoken;
3563                 ChessMove endtype = EndOfFile;
3564
3565                 if (tkind == 0) {
3566                   whitename = star_match[1];
3567                   blackname = star_match[2];
3568                   why = star_match[3];
3569                   endtoken = star_match[4];
3570                 } else {
3571                   whitename = star_match[1];
3572                   blackname = star_match[3];
3573                   why = star_match[5];
3574                   endtoken = star_match[6];
3575                 }
3576
3577                 /* Game start messages */
3578                 if (strncmp(why, "Creating ", 9) == 0 ||
3579                     strncmp(why, "Continuing ", 11) == 0) {
3580                     gs_gamenum = gamenum;
3581                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3582                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3583 #if ZIPPY
3584                     if (appData.zippyPlay) {
3585                         ZippyGameStart(whitename, blackname);
3586                     }
3587 #endif /*ZIPPY*/
3588                     partnerBoardValid = FALSE; // [HGM] bughouse
3589                     continue;
3590                 }
3591
3592                 /* Game end messages */
3593                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3594                     ics_gamenum != gamenum) {
3595                     continue;
3596                 }
3597                 while (endtoken[0] == ' ') endtoken++;
3598                 switch (endtoken[0]) {
3599                   case '*':
3600                   default:
3601                     endtype = GameUnfinished;
3602                     break;
3603                   case '0':
3604                     endtype = BlackWins;
3605                     break;
3606                   case '1':
3607                     if (endtoken[1] == '/')
3608                       endtype = GameIsDrawn;
3609                     else
3610                       endtype = WhiteWins;
3611                     break;
3612                 }
3613                 GameEnds(endtype, why, GE_ICS);
3614 #if ZIPPY
3615                 if (appData.zippyPlay && first.initDone) {
3616                     ZippyGameEnd(endtype, why);
3617                     if (first.pr == NULL) {
3618                       /* Start the next process early so that we'll
3619                          be ready for the next challenge */
3620                       StartChessProgram(&first);
3621                     }
3622                     /* Send "new" early, in case this command takes
3623                        a long time to finish, so that we'll be ready
3624                        for the next challenge. */
3625                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3626                     Reset(TRUE, TRUE);
3627                 }
3628 #endif /*ZIPPY*/
3629                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3630                 continue;
3631             }
3632
3633             if (looking_at(buf, &i, "Removing game * from observation") ||
3634                 looking_at(buf, &i, "no longer observing game *") ||
3635                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3636                 if (gameMode == IcsObserving &&
3637                     atoi(star_match[0]) == ics_gamenum)
3638                   {
3639                       /* icsEngineAnalyze */
3640                       if (appData.icsEngineAnalyze) {
3641                             ExitAnalyzeMode();
3642                             ModeHighlight();
3643                       }
3644                       StopClocks();
3645                       gameMode = IcsIdle;
3646                       ics_gamenum = -1;
3647                       ics_user_moved = FALSE;
3648                   }
3649                 continue;
3650             }
3651
3652             if (looking_at(buf, &i, "no longer examining game *")) {
3653                 if (gameMode == IcsExamining &&
3654                     atoi(star_match[0]) == ics_gamenum)
3655                   {
3656                       gameMode = IcsIdle;
3657                       ics_gamenum = -1;
3658                       ics_user_moved = FALSE;
3659                   }
3660                 continue;
3661             }
3662
3663             /* Advance leftover_start past any newlines we find,
3664                so only partial lines can get reparsed */
3665             if (looking_at(buf, &i, "\n")) {
3666                 prevColor = curColor;
3667                 if (curColor != ColorNormal) {
3668                     if (oldi > next_out) {
3669                         SendToPlayer(&buf[next_out], oldi - next_out);
3670                         next_out = oldi;
3671                     }
3672                     Colorize(ColorNormal, FALSE);
3673                     curColor = ColorNormal;
3674                 }
3675                 if (started == STARTED_BOARD) {
3676                     started = STARTED_NONE;
3677                     parse[parse_pos] = NULLCHAR;
3678                     ParseBoard12(parse);
3679                     ics_user_moved = 0;
3680
3681                     /* Send premove here */
3682                     if (appData.premove) {
3683                       char str[MSG_SIZ];
3684                       if (currentMove == 0 &&
3685                           gameMode == IcsPlayingWhite &&
3686                           appData.premoveWhite) {
3687                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3688                         if (appData.debugMode)
3689                           fprintf(debugFP, "Sending premove:\n");
3690                         SendToICS(str);
3691                       } else if (currentMove == 1 &&
3692                                  gameMode == IcsPlayingBlack &&
3693                                  appData.premoveBlack) {
3694                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3695                         if (appData.debugMode)
3696                           fprintf(debugFP, "Sending premove:\n");
3697                         SendToICS(str);
3698                       } else if (gotPremove) {
3699                         gotPremove = 0;
3700                         ClearPremoveHighlights();
3701                         if (appData.debugMode)
3702                           fprintf(debugFP, "Sending premove:\n");
3703                           UserMoveEvent(premoveFromX, premoveFromY,
3704                                         premoveToX, premoveToY,
3705                                         premovePromoChar);
3706                       }
3707                     }
3708
3709                     /* Usually suppress following prompt */
3710                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3711                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3712                         if (looking_at(buf, &i, "*% ")) {
3713                             savingComment = FALSE;
3714                             suppressKibitz = 0;
3715                         }
3716                     }
3717                     next_out = i;
3718                 } else if (started == STARTED_HOLDINGS) {
3719                     int gamenum;
3720                     char new_piece[MSG_SIZ];
3721                     started = STARTED_NONE;
3722                     parse[parse_pos] = NULLCHAR;
3723                     if (appData.debugMode)
3724                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3725                                                         parse, currentMove);
3726                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3727                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3728                         if (gameInfo.variant == VariantNormal) {
3729                           /* [HGM] We seem to switch variant during a game!
3730                            * Presumably no holdings were displayed, so we have
3731                            * to move the position two files to the right to
3732                            * create room for them!
3733                            */
3734                           VariantClass newVariant;
3735                           switch(gameInfo.boardWidth) { // base guess on board width
3736                                 case 9:  newVariant = VariantShogi; break;
3737                                 case 10: newVariant = VariantGreat; break;
3738                                 default: newVariant = VariantCrazyhouse; break;
3739                           }
3740                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3741                           /* Get a move list just to see the header, which
3742                              will tell us whether this is really bug or zh */
3743                           if (ics_getting_history == H_FALSE) {
3744                             ics_getting_history = H_REQUESTED;
3745                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3746                             SendToICS(str);
3747                           }
3748                         }
3749                         new_piece[0] = NULLCHAR;
3750                         sscanf(parse, "game %d white [%s black [%s <- %s",
3751                                &gamenum, white_holding, black_holding,
3752                                new_piece);
3753                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3754                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3755                         /* [HGM] copy holdings to board holdings area */
3756                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3757                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3758                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3759 #if ZIPPY
3760                         if (appData.zippyPlay && first.initDone) {
3761                             ZippyHoldings(white_holding, black_holding,
3762                                           new_piece);
3763                         }
3764 #endif /*ZIPPY*/
3765                         if (tinyLayout || smallLayout) {
3766                             char wh[16], bh[16];
3767                             PackHolding(wh, white_holding);
3768                             PackHolding(bh, black_holding);
3769                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3770                                     gameInfo.white, gameInfo.black);
3771                         } else {
3772                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3773                                     gameInfo.white, white_holding,
3774                                     gameInfo.black, black_holding);
3775                         }
3776                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3777                         DrawPosition(FALSE, boards[currentMove]);
3778                         DisplayTitle(str);
3779                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3780                         sscanf(parse, "game %d white [%s black [%s <- %s",
3781                                &gamenum, white_holding, black_holding,
3782                                new_piece);
3783                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3784                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3785                         /* [HGM] copy holdings to partner-board holdings area */
3786                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3787                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3788                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3789                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3790                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3791                       }
3792                     }
3793                     /* Suppress following prompt */
3794                     if (looking_at(buf, &i, "*% ")) {
3795                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3796                         savingComment = FALSE;
3797                         suppressKibitz = 0;
3798                     }
3799                     next_out = i;
3800                 }
3801                 continue;
3802             }
3803
3804             i++;                /* skip unparsed character and loop back */
3805         }
3806
3807         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3808 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3809 //          SendToPlayer(&buf[next_out], i - next_out);
3810             started != STARTED_HOLDINGS && leftover_start > next_out) {
3811             SendToPlayer(&buf[next_out], leftover_start - next_out);
3812             next_out = i;
3813         }
3814
3815         leftover_len = buf_len - leftover_start;
3816         /* if buffer ends with something we couldn't parse,
3817            reparse it after appending the next read */
3818
3819     } else if (count == 0) {
3820         RemoveInputSource(isr);
3821         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3822     } else {
3823         DisplayFatalError(_("Error reading from ICS"), error, 1);
3824     }
3825 }
3826
3827
3828 /* Board style 12 looks like this:
3829
3830    <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
3831
3832  * The "<12> " is stripped before it gets to this routine.  The two
3833  * trailing 0's (flip state and clock ticking) are later addition, and
3834  * some chess servers may not have them, or may have only the first.
3835  * Additional trailing fields may be added in the future.
3836  */
3837
3838 #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"
3839
3840 #define RELATION_OBSERVING_PLAYED    0
3841 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3842 #define RELATION_PLAYING_MYMOVE      1
3843 #define RELATION_PLAYING_NOTMYMOVE  -1
3844 #define RELATION_EXAMINING           2
3845 #define RELATION_ISOLATED_BOARD     -3
3846 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3847
3848 void
3849 ParseBoard12(string)
3850      char *string;
3851 {
3852     GameMode newGameMode;
3853     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3854     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3855     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3856     char to_play, board_chars[200];
3857     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3858     char black[32], white[32];
3859     Board board;
3860     int prevMove = currentMove;
3861     int ticking = 2;
3862     ChessMove moveType;
3863     int fromX, fromY, toX, toY;
3864     char promoChar;
3865     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3866     char *bookHit = NULL; // [HGM] book
3867     Boolean weird = FALSE, reqFlag = FALSE;
3868
3869     fromX = fromY = toX = toY = -1;
3870
3871     newGame = FALSE;
3872
3873     if (appData.debugMode)
3874       fprintf(debugFP, _("Parsing board: %s\n"), string);
3875
3876     move_str[0] = NULLCHAR;
3877     elapsed_time[0] = NULLCHAR;
3878     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3879         int  i = 0, j;
3880         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3881             if(string[i] == ' ') { ranks++; files = 0; }
3882             else files++;
3883             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3884             i++;
3885         }
3886         for(j = 0; j <i; j++) board_chars[j] = string[j];
3887         board_chars[i] = '\0';
3888         string += i + 1;
3889     }
3890     n = sscanf(string, PATTERN, &to_play, &double_push,
3891                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3892                &gamenum, white, black, &relation, &basetime, &increment,
3893                &white_stren, &black_stren, &white_time, &black_time,
3894                &moveNum, str, elapsed_time, move_str, &ics_flip,
3895                &ticking);
3896
3897     if (n < 21) {
3898         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3899         DisplayError(str, 0);
3900         return;
3901     }
3902
3903     /* Convert the move number to internal form */
3904     moveNum = (moveNum - 1) * 2;
3905     if (to_play == 'B') moveNum++;
3906     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3907       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3908                         0, 1);
3909       return;
3910     }
3911
3912     switch (relation) {
3913       case RELATION_OBSERVING_PLAYED:
3914       case RELATION_OBSERVING_STATIC:
3915         if (gamenum == -1) {
3916             /* Old ICC buglet */
3917             relation = RELATION_OBSERVING_STATIC;
3918         }
3919         newGameMode = IcsObserving;
3920         break;
3921       case RELATION_PLAYING_MYMOVE:
3922       case RELATION_PLAYING_NOTMYMOVE:
3923         newGameMode =
3924           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3925             IcsPlayingWhite : IcsPlayingBlack;
3926         break;
3927       case RELATION_EXAMINING:
3928         newGameMode = IcsExamining;
3929         break;
3930       case RELATION_ISOLATED_BOARD:
3931       default:
3932         /* Just display this board.  If user was doing something else,
3933            we will forget about it until the next board comes. */
3934         newGameMode = IcsIdle;
3935         break;
3936       case RELATION_STARTING_POSITION:
3937         newGameMode = gameMode;
3938         break;
3939     }
3940
3941     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3942          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3943       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3944       char *toSqr;
3945       for (k = 0; k < ranks; k++) {
3946         for (j = 0; j < files; j++)
3947           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3948         if(gameInfo.holdingsWidth > 1) {
3949              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3950              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3951         }
3952       }
3953       CopyBoard(partnerBoard, board);
3954       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3955         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3956         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3957       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3958       if(toSqr = strchr(str, '-')) {
3959         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3960         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3961       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3962       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3963       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3964       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3965       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3966       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3967                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3968       DisplayMessage(partnerStatus, "");
3969         partnerBoardValid = TRUE;
3970       return;
3971     }
3972
3973     /* Modify behavior for initial board display on move listing
3974        of wild games.
3975        */
3976     switch (ics_getting_history) {
3977       case H_FALSE:
3978       case H_REQUESTED:
3979         break;
3980       case H_GOT_REQ_HEADER:
3981       case H_GOT_UNREQ_HEADER:
3982         /* This is the initial position of the current game */
3983         gamenum = ics_gamenum;
3984         moveNum = 0;            /* old ICS bug workaround */
3985         if (to_play == 'B') {
3986           startedFromSetupPosition = TRUE;
3987           blackPlaysFirst = TRUE;
3988           moveNum = 1;
3989           if (forwardMostMove == 0) forwardMostMove = 1;
3990           if (backwardMostMove == 0) backwardMostMove = 1;
3991           if (currentMove == 0) currentMove = 1;
3992         }
3993         newGameMode = gameMode;
3994         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3995         break;
3996       case H_GOT_UNWANTED_HEADER:
3997         /* This is an initial board that we don't want */
3998         return;
3999       case H_GETTING_MOVES:
4000         /* Should not happen */
4001         DisplayError(_("Error gathering move list: extra board"), 0);
4002         ics_getting_history = H_FALSE;
4003         return;
4004     }
4005
4006    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4007                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4008      /* [HGM] We seem to have switched variant unexpectedly
4009       * Try to guess new variant from board size
4010       */
4011           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4012           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4013           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4014           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4015           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4016           if(!weird) newVariant = VariantNormal;
4017           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4018           /* Get a move list just to see the header, which
4019              will tell us whether this is really bug or zh */
4020           if (ics_getting_history == H_FALSE) {
4021             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4022             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023             SendToICS(str);
4024           }
4025     }
4026
4027     /* Take action if this is the first board of a new game, or of a
4028        different game than is currently being displayed.  */
4029     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4030         relation == RELATION_ISOLATED_BOARD) {
4031
4032         /* Forget the old game and get the history (if any) of the new one */
4033         if (gameMode != BeginningOfGame) {
4034           Reset(TRUE, TRUE);
4035         }
4036         newGame = TRUE;
4037         if (appData.autoRaiseBoard) BoardToTop();
4038         prevMove = -3;
4039         if (gamenum == -1) {
4040             newGameMode = IcsIdle;
4041         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4042                    appData.getMoveList && !reqFlag) {
4043             /* Need to get game history */
4044             ics_getting_history = H_REQUESTED;
4045             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4046             SendToICS(str);
4047         }
4048
4049         /* Initially flip the board to have black on the bottom if playing
4050            black or if the ICS flip flag is set, but let the user change
4051            it with the Flip View button. */
4052         flipView = appData.autoFlipView ?
4053           (newGameMode == IcsPlayingBlack) || ics_flip :
4054           appData.flipView;
4055
4056         /* Done with values from previous mode; copy in new ones */
4057         gameMode = newGameMode;
4058         ModeHighlight();
4059         ics_gamenum = gamenum;
4060         if (gamenum == gs_gamenum) {
4061             int klen = strlen(gs_kind);
4062             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4063             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4064             gameInfo.event = StrSave(str);
4065         } else {
4066             gameInfo.event = StrSave("ICS game");
4067         }
4068         gameInfo.site = StrSave(appData.icsHost);
4069         gameInfo.date = PGNDate();
4070         gameInfo.round = StrSave("-");
4071         gameInfo.white = StrSave(white);
4072         gameInfo.black = StrSave(black);
4073         timeControl = basetime * 60 * 1000;
4074         timeControl_2 = 0;
4075         timeIncrement = increment * 1000;
4076         movesPerSession = 0;
4077         gameInfo.timeControl = TimeControlTagValue();
4078         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4079   if (appData.debugMode) {
4080     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4081     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4082     setbuf(debugFP, NULL);
4083   }
4084
4085         gameInfo.outOfBook = NULL;
4086
4087         /* Do we have the ratings? */
4088         if (strcmp(player1Name, white) == 0 &&
4089             strcmp(player2Name, black) == 0) {
4090             if (appData.debugMode)
4091               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4092                       player1Rating, player2Rating);
4093             gameInfo.whiteRating = player1Rating;
4094             gameInfo.blackRating = player2Rating;
4095         } else if (strcmp(player2Name, white) == 0 &&
4096                    strcmp(player1Name, black) == 0) {
4097             if (appData.debugMode)
4098               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4099                       player2Rating, player1Rating);
4100             gameInfo.whiteRating = player2Rating;
4101             gameInfo.blackRating = player1Rating;
4102         }
4103         player1Name[0] = player2Name[0] = NULLCHAR;
4104
4105         /* Silence shouts if requested */
4106         if (appData.quietPlay &&
4107             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4108             SendToICS(ics_prefix);
4109             SendToICS("set shout 0\n");
4110         }
4111     }
4112
4113     /* Deal with midgame name changes */
4114     if (!newGame) {
4115         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4116             if (gameInfo.white) free(gameInfo.white);
4117             gameInfo.white = StrSave(white);
4118         }
4119         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4120             if (gameInfo.black) free(gameInfo.black);
4121             gameInfo.black = StrSave(black);
4122         }
4123     }
4124
4125     /* Throw away game result if anything actually changes in examine mode */
4126     if (gameMode == IcsExamining && !newGame) {
4127         gameInfo.result = GameUnfinished;
4128         if (gameInfo.resultDetails != NULL) {
4129             free(gameInfo.resultDetails);
4130             gameInfo.resultDetails = NULL;
4131         }
4132     }
4133
4134     /* In pausing && IcsExamining mode, we ignore boards coming
4135        in if they are in a different variation than we are. */
4136     if (pauseExamInvalid) return;
4137     if (pausing && gameMode == IcsExamining) {
4138         if (moveNum <= pauseExamForwardMostMove) {
4139             pauseExamInvalid = TRUE;
4140             forwardMostMove = pauseExamForwardMostMove;
4141             return;
4142         }
4143     }
4144
4145   if (appData.debugMode) {
4146     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4147   }
4148     /* Parse the board */
4149     for (k = 0; k < ranks; k++) {
4150       for (j = 0; j < files; j++)
4151         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4152       if(gameInfo.holdingsWidth > 1) {
4153            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4154            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4155       }
4156     }
4157     CopyBoard(boards[moveNum], board);
4158     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4159     if (moveNum == 0) {
4160         startedFromSetupPosition =
4161           !CompareBoards(board, initialPosition);
4162         if(startedFromSetupPosition)
4163             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4164     }
4165
4166     /* [HGM] Set castling rights. Take the outermost Rooks,
4167        to make it also work for FRC opening positions. Note that board12
4168        is really defective for later FRC positions, as it has no way to
4169        indicate which Rook can castle if they are on the same side of King.
4170        For the initial position we grant rights to the outermost Rooks,
4171        and remember thos rights, and we then copy them on positions
4172        later in an FRC game. This means WB might not recognize castlings with
4173        Rooks that have moved back to their original position as illegal,
4174        but in ICS mode that is not its job anyway.
4175     */
4176     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4177     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4178
4179         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4180             if(board[0][i] == WhiteRook) j = i;
4181         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4183             if(board[0][i] == WhiteRook) j = i;
4184         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4185         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4186             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4187         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4188         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4189             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4190         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4191
4192         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4193         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4194             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4195         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4196             if(board[BOARD_HEIGHT-1][k] == bKing)
4197                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4198         if(gameInfo.variant == VariantTwoKings) {
4199             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4200             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4201             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4202         }
4203     } else { int r;
4204         r = boards[moveNum][CASTLING][0] = initialRights[0];
4205         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4206         r = boards[moveNum][CASTLING][1] = initialRights[1];
4207         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4208         r = boards[moveNum][CASTLING][3] = initialRights[3];
4209         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4210         r = boards[moveNum][CASTLING][4] = initialRights[4];
4211         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4212         /* wildcastle kludge: always assume King has rights */
4213         r = boards[moveNum][CASTLING][2] = initialRights[2];
4214         r = boards[moveNum][CASTLING][5] = initialRights[5];
4215     }
4216     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4217     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4218
4219
4220     if (ics_getting_history == H_GOT_REQ_HEADER ||
4221         ics_getting_history == H_GOT_UNREQ_HEADER) {
4222         /* This was an initial position from a move list, not
4223            the current position */
4224         return;
4225     }
4226
4227     /* Update currentMove and known move number limits */
4228     newMove = newGame || moveNum > forwardMostMove;
4229
4230     if (newGame) {
4231         forwardMostMove = backwardMostMove = currentMove = moveNum;
4232         if (gameMode == IcsExamining && moveNum == 0) {
4233           /* Workaround for ICS limitation: we are not told the wild
4234              type when starting to examine a game.  But if we ask for
4235              the move list, the move list header will tell us */
4236             ics_getting_history = H_REQUESTED;
4237             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4238             SendToICS(str);
4239         }
4240     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4241                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4242 #if ZIPPY
4243         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4244         /* [HGM] applied this also to an engine that is silently watching        */
4245         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4246             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4247             gameInfo.variant == currentlyInitializedVariant) {
4248           takeback = forwardMostMove - moveNum;
4249           for (i = 0; i < takeback; i++) {
4250             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4251             SendToProgram("undo\n", &first);
4252           }
4253         }
4254 #endif
4255
4256         forwardMostMove = moveNum;
4257         if (!pausing || currentMove > forwardMostMove)
4258           currentMove = forwardMostMove;
4259     } else {
4260         /* New part of history that is not contiguous with old part */
4261         if (pausing && gameMode == IcsExamining) {
4262             pauseExamInvalid = TRUE;
4263             forwardMostMove = pauseExamForwardMostMove;
4264             return;
4265         }
4266         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4267 #if ZIPPY
4268             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4269                 // [HGM] when we will receive the move list we now request, it will be
4270                 // fed to the engine from the first move on. So if the engine is not
4271                 // in the initial position now, bring it there.
4272                 InitChessProgram(&first, 0);
4273             }
4274 #endif
4275             ics_getting_history = H_REQUESTED;
4276             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4277             SendToICS(str);
4278         }
4279         forwardMostMove = backwardMostMove = currentMove = moveNum;
4280     }
4281
4282     /* Update the clocks */
4283     if (strchr(elapsed_time, '.')) {
4284       /* Time is in ms */
4285       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4286       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4287     } else {
4288       /* Time is in seconds */
4289       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4290       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4291     }
4292
4293
4294 #if ZIPPY
4295     if (appData.zippyPlay && newGame &&
4296         gameMode != IcsObserving && gameMode != IcsIdle &&
4297         gameMode != IcsExamining)
4298       ZippyFirstBoard(moveNum, basetime, increment);
4299 #endif
4300
4301     /* Put the move on the move list, first converting
4302        to canonical algebraic form. */
4303     if (moveNum > 0) {
4304   if (appData.debugMode) {
4305     if (appData.debugMode) { int f = forwardMostMove;
4306         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4307                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4308                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4309     }
4310     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4311     fprintf(debugFP, "moveNum = %d\n", moveNum);
4312     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4313     setbuf(debugFP, NULL);
4314   }
4315         if (moveNum <= backwardMostMove) {
4316             /* We don't know what the board looked like before
4317                this move.  Punt. */
4318           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4319             strcat(parseList[moveNum - 1], " ");
4320             strcat(parseList[moveNum - 1], elapsed_time);
4321             moveList[moveNum - 1][0] = NULLCHAR;
4322         } else if (strcmp(move_str, "none") == 0) {
4323             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4324             /* Again, we don't know what the board looked like;
4325                this is really the start of the game. */
4326             parseList[moveNum - 1][0] = NULLCHAR;
4327             moveList[moveNum - 1][0] = NULLCHAR;
4328             backwardMostMove = moveNum;
4329             startedFromSetupPosition = TRUE;
4330             fromX = fromY = toX = toY = -1;
4331         } else {
4332           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4333           //                 So we parse the long-algebraic move string in stead of the SAN move
4334           int valid; char buf[MSG_SIZ], *prom;
4335
4336           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4337                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4338           // str looks something like "Q/a1-a2"; kill the slash
4339           if(str[1] == '/')
4340             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4341           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4342           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4343                 strcat(buf, prom); // long move lacks promo specification!
4344           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4345                 if(appData.debugMode)
4346                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4347                 safeStrCpy(move_str, buf, MSG_SIZ);
4348           }
4349           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4350                                 &fromX, &fromY, &toX, &toY, &promoChar)
4351                || ParseOneMove(buf, moveNum - 1, &moveType,
4352                                 &fromX, &fromY, &toX, &toY, &promoChar);
4353           // end of long SAN patch
4354           if (valid) {
4355             (void) CoordsToAlgebraic(boards[moveNum - 1],
4356                                      PosFlags(moveNum - 1),
4357                                      fromY, fromX, toY, toX, promoChar,
4358                                      parseList[moveNum-1]);
4359             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4360               case MT_NONE:
4361               case MT_STALEMATE:
4362               default:
4363                 break;
4364               case MT_CHECK:
4365                 if(gameInfo.variant != VariantShogi)
4366                     strcat(parseList[moveNum - 1], "+");
4367                 break;
4368               case MT_CHECKMATE:
4369               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4370                 strcat(parseList[moveNum - 1], "#");
4371                 break;
4372             }
4373             strcat(parseList[moveNum - 1], " ");
4374             strcat(parseList[moveNum - 1], elapsed_time);
4375             /* currentMoveString is set as a side-effect of ParseOneMove */
4376             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4377             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4378             strcat(moveList[moveNum - 1], "\n");
4379
4380             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4381                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4382               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4383                 ChessSquare old, new = boards[moveNum][k][j];
4384                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4385                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4386                   if(old == new) continue;
4387                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4388                   else if(new == WhiteWazir || new == BlackWazir) {
4389                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4390                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4391                       else boards[moveNum][k][j] = old; // preserve type of Gold
4392                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4393                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4394               }
4395           } else {
4396             /* Move from ICS was illegal!?  Punt. */
4397             if (appData.debugMode) {
4398               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4399               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4400             }
4401             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4402             strcat(parseList[moveNum - 1], " ");
4403             strcat(parseList[moveNum - 1], elapsed_time);
4404             moveList[moveNum - 1][0] = NULLCHAR;
4405             fromX = fromY = toX = toY = -1;
4406           }
4407         }
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4410     setbuf(debugFP, NULL);
4411   }
4412
4413 #if ZIPPY
4414         /* Send move to chess program (BEFORE animating it). */
4415         if (appData.zippyPlay && !newGame && newMove &&
4416            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4417
4418             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4419                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4420                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4421                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4422                             move_str);
4423                     DisplayError(str, 0);
4424                 } else {
4425                     if (first.sendTime) {
4426                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4427                     }
4428                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4429                     if (firstMove && !bookHit) {
4430                         firstMove = FALSE;
4431                         if (first.useColors) {
4432                           SendToProgram(gameMode == IcsPlayingWhite ?
4433                                         "white\ngo\n" :
4434                                         "black\ngo\n", &first);
4435                         } else {
4436                           SendToProgram("go\n", &first);
4437                         }
4438                         first.maybeThinking = TRUE;
4439                     }
4440                 }
4441             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4442               if (moveList[moveNum - 1][0] == NULLCHAR) {
4443                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4444                 DisplayError(str, 0);
4445               } else {
4446                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4447                 SendMoveToProgram(moveNum - 1, &first);
4448               }
4449             }
4450         }
4451 #endif
4452     }
4453
4454     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4455         /* If move comes from a remote source, animate it.  If it
4456            isn't remote, it will have already been animated. */
4457         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4458             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4459         }
4460         if (!pausing && appData.highlightLastMove) {
4461             SetHighlights(fromX, fromY, toX, toY);
4462         }
4463     }
4464
4465     /* Start the clocks */
4466     whiteFlag = blackFlag = FALSE;
4467     appData.clockMode = !(basetime == 0 && increment == 0);
4468     if (ticking == 0) {
4469       ics_clock_paused = TRUE;
4470       StopClocks();
4471     } else if (ticking == 1) {
4472       ics_clock_paused = FALSE;
4473     }
4474     if (gameMode == IcsIdle ||
4475         relation == RELATION_OBSERVING_STATIC ||
4476         relation == RELATION_EXAMINING ||
4477         ics_clock_paused)
4478       DisplayBothClocks();
4479     else
4480       StartClocks();
4481
4482     /* Display opponents and material strengths */
4483     if (gameInfo.variant != VariantBughouse &&
4484         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4485         if (tinyLayout || smallLayout) {
4486             if(gameInfo.variant == VariantNormal)
4487               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4488                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4489                     basetime, increment);
4490             else
4491               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4492                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4493                     basetime, increment, (int) gameInfo.variant);
4494         } else {
4495             if(gameInfo.variant == VariantNormal)
4496               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4497                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4498                     basetime, increment);
4499             else
4500               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4501                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4502                     basetime, increment, VariantName(gameInfo.variant));
4503         }
4504         DisplayTitle(str);
4505   if (appData.debugMode) {
4506     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4507   }
4508     }
4509
4510
4511     /* Display the board */
4512     if (!pausing && !appData.noGUI) {
4513
4514       if (appData.premove)
4515           if (!gotPremove ||
4516              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4517              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4518               ClearPremoveHighlights();
4519
4520       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4521         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4522       DrawPosition(j, boards[currentMove]);
4523
4524       DisplayMove(moveNum - 1);
4525       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4526             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4527               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4528         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4529       }
4530     }
4531
4532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4533 #if ZIPPY
4534     if(bookHit) { // [HGM] book: simulate book reply
4535         static char bookMove[MSG_SIZ]; // a bit generous?
4536
4537         programStats.nodes = programStats.depth = programStats.time =
4538         programStats.score = programStats.got_only_move = 0;
4539         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4540
4541         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4542         strcat(bookMove, bookHit);
4543         HandleMachineMove(bookMove, &first);
4544     }
4545 #endif
4546 }
4547
4548 void
4549 GetMoveListEvent()
4550 {
4551     char buf[MSG_SIZ];
4552     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4553         ics_getting_history = H_REQUESTED;
4554         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4555         SendToICS(buf);
4556     }
4557 }
4558
4559 void
4560 AnalysisPeriodicEvent(force)
4561      int force;
4562 {
4563     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4564          && !force) || !appData.periodicUpdates)
4565       return;
4566
4567     /* Send . command to Crafty to collect stats */
4568     SendToProgram(".\n", &first);
4569
4570     /* Don't send another until we get a response (this makes
4571        us stop sending to old Crafty's which don't understand
4572        the "." command (sending illegal cmds resets node count & time,
4573        which looks bad)) */
4574     programStats.ok_to_send = 0;
4575 }
4576
4577 void ics_update_width(new_width)
4578         int new_width;
4579 {
4580         ics_printf("set width %d\n", new_width);
4581 }
4582
4583 void
4584 SendMoveToProgram(moveNum, cps)
4585      int moveNum;
4586      ChessProgramState *cps;
4587 {
4588     char buf[MSG_SIZ];
4589
4590     if (cps->useUsermove) {
4591       SendToProgram("usermove ", cps);
4592     }
4593     if (cps->useSAN) {
4594       char *space;
4595       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4596         int len = space - parseList[moveNum];
4597         memcpy(buf, parseList[moveNum], len);
4598         buf[len++] = '\n';
4599         buf[len] = NULLCHAR;
4600       } else {
4601         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4602       }
4603       SendToProgram(buf, cps);
4604     } else {
4605       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4606         AlphaRank(moveList[moveNum], 4);
4607         SendToProgram(moveList[moveNum], cps);
4608         AlphaRank(moveList[moveNum], 4); // and back
4609       } else
4610       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4611        * the engine. It would be nice to have a better way to identify castle
4612        * moves here. */
4613       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4614                                                                          && cps->useOOCastle) {
4615         int fromX = moveList[moveNum][0] - AAA;
4616         int fromY = moveList[moveNum][1] - ONE;
4617         int toX = moveList[moveNum][2] - AAA;
4618         int toY = moveList[moveNum][3] - ONE;
4619         if((boards[moveNum][fromY][fromX] == WhiteKing
4620             && boards[moveNum][toY][toX] == WhiteRook)
4621            || (boards[moveNum][fromY][fromX] == BlackKing
4622                && boards[moveNum][toY][toX] == BlackRook)) {
4623           if(toX > fromX) SendToProgram("O-O\n", cps);
4624           else SendToProgram("O-O-O\n", cps);
4625         }
4626         else SendToProgram(moveList[moveNum], cps);
4627       }
4628       else SendToProgram(moveList[moveNum], cps);
4629       /* End of additions by Tord */
4630     }
4631
4632     /* [HGM] setting up the opening has brought engine in force mode! */
4633     /*       Send 'go' if we are in a mode where machine should play. */
4634     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4635         (gameMode == TwoMachinesPlay   ||
4636 #if ZIPPY
4637          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4638 #endif
4639          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4640         SendToProgram("go\n", cps);
4641   if (appData.debugMode) {
4642     fprintf(debugFP, "(extra)\n");
4643   }
4644     }
4645     setboardSpoiledMachineBlack = 0;
4646 }
4647
4648 void
4649 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4650      ChessMove moveType;
4651      int fromX, fromY, toX, toY;
4652      char promoChar;
4653 {
4654     char user_move[MSG_SIZ];
4655
4656     switch (moveType) {
4657       default:
4658         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4659                 (int)moveType, fromX, fromY, toX, toY);
4660         DisplayError(user_move + strlen("say "), 0);
4661         break;
4662       case WhiteKingSideCastle:
4663       case BlackKingSideCastle:
4664       case WhiteQueenSideCastleWild:
4665       case BlackQueenSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteHSideCastleFR:
4668       case BlackHSideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o\n");
4671         break;
4672       case WhiteQueenSideCastle:
4673       case BlackQueenSideCastle:
4674       case WhiteKingSideCastleWild:
4675       case BlackKingSideCastleWild:
4676       /* PUSH Fabien */
4677       case WhiteASideCastleFR:
4678       case BlackASideCastleFR:
4679       /* POP Fabien */
4680         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4681         break;
4682       case WhiteNonPromotion:
4683       case BlackNonPromotion:
4684         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4685         break;
4686       case WhitePromotion:
4687       case BlackPromotion:
4688         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4689           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4690                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4691                 PieceToChar(WhiteFerz));
4692         else if(gameInfo.variant == VariantGreat)
4693           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4694                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4695                 PieceToChar(WhiteMan));
4696         else
4697           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4698                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4699                 promoChar);
4700         break;
4701       case WhiteDrop:
4702       case BlackDrop:
4703       drop:
4704         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4705                  ToUpper(PieceToChar((ChessSquare) fromX)),
4706                  AAA + toX, ONE + toY);
4707         break;
4708       case IllegalMove:  /* could be a variant we don't quite understand */
4709         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4710       case NormalMove:
4711       case WhiteCapturesEnPassant:
4712       case BlackCapturesEnPassant:
4713         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4714                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4715         break;
4716     }
4717     SendToICS(user_move);
4718     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4719         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4720 }
4721
4722 void
4723 UploadGameEvent()
4724 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4725     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4726     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4727     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4728         DisplayError("You cannot do this while you are playing or observing", 0);
4729         return;
4730     }
4731     if(gameMode != IcsExamining) { // is this ever not the case?
4732         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4733
4734         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4735           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4736         } else { // on FICS we must first go to general examine mode
4737           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4738         }
4739         if(gameInfo.variant != VariantNormal) {
4740             // try figure out wild number, as xboard names are not always valid on ICS
4741             for(i=1; i<=36; i++) {
4742               snprintf(buf, MSG_SIZ, "wild/%d", i);
4743                 if(StringToVariant(buf) == gameInfo.variant) break;
4744             }
4745             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4746             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4747             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4748         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4749         SendToICS(ics_prefix);
4750         SendToICS(buf);
4751         if(startedFromSetupPosition || backwardMostMove != 0) {
4752           fen = PositionToFEN(backwardMostMove, NULL);
4753           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4754             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4755             SendToICS(buf);
4756           } else { // FICS: everything has to set by separate bsetup commands
4757             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4758             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4759             SendToICS(buf);
4760             if(!WhiteOnMove(backwardMostMove)) {
4761                 SendToICS("bsetup tomove black\n");
4762             }
4763             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4764             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4765             SendToICS(buf);
4766             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4767             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4768             SendToICS(buf);
4769             i = boards[backwardMostMove][EP_STATUS];
4770             if(i >= 0) { // set e.p.
4771               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4772                 SendToICS(buf);
4773             }
4774             bsetup++;
4775           }
4776         }
4777       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4778             SendToICS("bsetup done\n"); // switch to normal examining.
4779     }
4780     for(i = backwardMostMove; i<last; i++) {
4781         char buf[20];
4782         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4783         SendToICS(buf);
4784     }
4785     SendToICS(ics_prefix);
4786     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4787 }
4788
4789 void
4790 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4791      int rf, ff, rt, ft;
4792      char promoChar;
4793      char move[7];
4794 {
4795     if (rf == DROP_RANK) {
4796       sprintf(move, "%c@%c%c\n",
4797                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4798     } else {
4799         if (promoChar == 'x' || promoChar == NULLCHAR) {
4800           sprintf(move, "%c%c%c%c\n",
4801                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4802         } else {
4803             sprintf(move, "%c%c%c%c%c\n",
4804                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4805         }
4806     }
4807 }
4808
4809 void
4810 ProcessICSInitScript(f)
4811      FILE *f;
4812 {
4813     char buf[MSG_SIZ];
4814
4815     while (fgets(buf, MSG_SIZ, f)) {
4816         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4817     }
4818
4819     fclose(f);
4820 }
4821
4822
4823 static int lastX, lastY, selectFlag, dragging;
4824
4825 void
4826 Sweep(int step)
4827 {
4828     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4829     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4830     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4831     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4832     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4833     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4834     do {
4835         promoSweep -= step;
4836         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4837         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4838         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4839         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4840         if(!step) step = 1;
4841     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4842             appData.testLegality && (promoSweep == king ||
4843             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4844     ChangeDragPiece(promoSweep);
4845 }
4846
4847 int PromoScroll(int x, int y)
4848 {
4849   int step = 0;
4850
4851   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4852   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4853   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4854   if(!step) return FALSE;
4855   lastX = x; lastY = y;
4856   if((promoSweep < BlackPawn) == flipView) step = -step;
4857   if(step > 0) selectFlag = 1;
4858   if(!selectFlag) Sweep(step);
4859   return FALSE;
4860 }
4861
4862 void
4863 NextPiece(int step)
4864 {
4865     ChessSquare piece = boards[currentMove][toY][toX];
4866     do {
4867         pieceSweep -= step;
4868         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4869         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4870         if(!step) step = -1;
4871     } while(PieceToChar(pieceSweep) == '.');
4872     boards[currentMove][toY][toX] = pieceSweep;
4873     DrawPosition(FALSE, boards[currentMove]);
4874     boards[currentMove][toY][toX] = piece;
4875 }
4876 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4877 void
4878 AlphaRank(char *move, int n)
4879 {
4880 //    char *p = move, c; int x, y;
4881
4882     if (appData.debugMode) {
4883         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4884     }
4885
4886     if(move[1]=='*' &&
4887        move[2]>='0' && move[2]<='9' &&
4888        move[3]>='a' && move[3]<='x'    ) {
4889         move[1] = '@';
4890         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4891         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4892     } else
4893     if(move[0]>='0' && move[0]<='9' &&
4894        move[1]>='a' && move[1]<='x' &&
4895        move[2]>='0' && move[2]<='9' &&
4896        move[3]>='a' && move[3]<='x'    ) {
4897         /* input move, Shogi -> normal */
4898         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4899         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4900         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4901         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4902     } else
4903     if(move[1]=='@' &&
4904        move[3]>='0' && move[3]<='9' &&
4905        move[2]>='a' && move[2]<='x'    ) {
4906         move[1] = '*';
4907         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4908         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4909     } else
4910     if(
4911        move[0]>='a' && move[0]<='x' &&
4912        move[3]>='0' && move[3]<='9' &&
4913        move[2]>='a' && move[2]<='x'    ) {
4914          /* output move, normal -> Shogi */
4915         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4916         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4917         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4918         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4919         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4920     }
4921     if (appData.debugMode) {
4922         fprintf(debugFP, "   out = '%s'\n", move);
4923     }
4924 }
4925
4926 char yy_textstr[8000];
4927
4928 /* Parser for moves from gnuchess, ICS, or user typein box */
4929 Boolean
4930 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4931      char *move;
4932      int moveNum;
4933      ChessMove *moveType;
4934      int *fromX, *fromY, *toX, *toY;
4935      char *promoChar;
4936 {
4937     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4938
4939     switch (*moveType) {
4940       case WhitePromotion:
4941       case BlackPromotion:
4942       case WhiteNonPromotion:
4943       case BlackNonPromotion:
4944       case NormalMove:
4945       case WhiteCapturesEnPassant:
4946       case BlackCapturesEnPassant:
4947       case WhiteKingSideCastle:
4948       case WhiteQueenSideCastle:
4949       case BlackKingSideCastle:
4950       case BlackQueenSideCastle:
4951       case WhiteKingSideCastleWild:
4952       case WhiteQueenSideCastleWild:
4953       case BlackKingSideCastleWild:
4954       case BlackQueenSideCastleWild:
4955       /* Code added by Tord: */
4956       case WhiteHSideCastleFR:
4957       case WhiteASideCastleFR:
4958       case BlackHSideCastleFR:
4959       case BlackASideCastleFR:
4960       /* End of code added by Tord */
4961       case IllegalMove:         /* bug or odd chess variant */
4962         *fromX = currentMoveString[0] - AAA;
4963         *fromY = currentMoveString[1] - ONE;
4964         *toX = currentMoveString[2] - AAA;
4965         *toY = currentMoveString[3] - ONE;
4966         *promoChar = currentMoveString[4];
4967         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4968             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4969     if (appData.debugMode) {
4970         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4971     }
4972             *fromX = *fromY = *toX = *toY = 0;
4973             return FALSE;
4974         }
4975         if (appData.testLegality) {
4976           return (*moveType != IllegalMove);
4977         } else {
4978           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4979                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4980         }
4981
4982       case WhiteDrop:
4983       case BlackDrop:
4984         *fromX = *moveType == WhiteDrop ?
4985           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4986           (int) CharToPiece(ToLower(currentMoveString[0]));
4987         *fromY = DROP_RANK;
4988         *toX = currentMoveString[2] - AAA;
4989         *toY = currentMoveString[3] - ONE;
4990         *promoChar = NULLCHAR;
4991         return TRUE;
4992
4993       case AmbiguousMove:
4994       case ImpossibleMove:
4995       case EndOfFile:
4996       case ElapsedTime:
4997       case Comment:
4998       case PGNTag:
4999       case NAG:
5000       case WhiteWins:
5001       case BlackWins:
5002       case GameIsDrawn:
5003       default:
5004     if (appData.debugMode) {
5005         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5006     }
5007         /* bug? */
5008         *fromX = *fromY = *toX = *toY = 0;
5009         *promoChar = NULLCHAR;
5010         return FALSE;
5011     }
5012 }
5013
5014
5015 void
5016 ParsePV(char *pv, Boolean storeComments)
5017 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5018   int fromX, fromY, toX, toY; char promoChar;
5019   ChessMove moveType;
5020   Boolean valid;
5021   int nr = 0;
5022
5023   endPV = forwardMostMove;
5024   do {
5025     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5026     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5027     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5028 if(appData.debugMode){
5029 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);
5030 }
5031     if(!valid && nr == 0 &&
5032        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5033         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5034         // Hande case where played move is different from leading PV move
5035         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5036         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5037         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5038         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5039           endPV += 2; // if position different, keep this
5040           moveList[endPV-1][0] = fromX + AAA;
5041           moveList[endPV-1][1] = fromY + ONE;
5042           moveList[endPV-1][2] = toX + AAA;
5043           moveList[endPV-1][3] = toY + ONE;
5044           parseList[endPV-1][0] = NULLCHAR;
5045           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5046         }
5047       }
5048     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5049     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5050     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5051     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5052         valid++; // allow comments in PV
5053         continue;
5054     }
5055     nr++;
5056     if(endPV+1 > framePtr) break; // no space, truncate
5057     if(!valid) break;
5058     endPV++;
5059     CopyBoard(boards[endPV], boards[endPV-1]);
5060     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5061     moveList[endPV-1][0] = fromX + AAA;
5062     moveList[endPV-1][1] = fromY + ONE;
5063     moveList[endPV-1][2] = toX + AAA;
5064     moveList[endPV-1][3] = toY + ONE;
5065     moveList[endPV-1][4] = promoChar;
5066     moveList[endPV-1][5] = NULLCHAR;
5067     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5068     if(storeComments)
5069         CoordsToAlgebraic(boards[endPV - 1],
5070                              PosFlags(endPV - 1),
5071                              fromY, fromX, toY, toX, promoChar,
5072                              parseList[endPV - 1]);
5073     else
5074         parseList[endPV-1][0] = NULLCHAR;
5075   } while(valid);
5076   currentMove = endPV;
5077   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5078   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5079                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5080   DrawPosition(TRUE, boards[currentMove]);
5081 }
5082
5083 Boolean
5084 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5085 {
5086         int startPV;
5087         char *p;
5088
5089         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5090         lastX = x; lastY = y;
5091         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5092         startPV = index;
5093         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5094         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5095         index = startPV;
5096         do{ while(buf[index] && buf[index] != '\n') index++;
5097         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5098         buf[index] = 0;
5099         ParsePV(buf+startPV, FALSE);
5100         *start = startPV; *end = index-1;
5101         return TRUE;
5102 }
5103
5104 Boolean
5105 LoadPV(int x, int y)
5106 { // called on right mouse click to load PV
5107   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5108   lastX = x; lastY = y;
5109   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5110   return TRUE;
5111 }
5112
5113 void
5114 UnLoadPV()
5115 {
5116   if(endPV < 0) return;
5117   endPV = -1;
5118   currentMove = forwardMostMove;
5119   ClearPremoveHighlights();
5120   DrawPosition(TRUE, boards[currentMove]);
5121 }
5122
5123 void
5124 MovePV(int x, int y, int h)
5125 { // step through PV based on mouse coordinates (called on mouse move)
5126   int margin = h>>3, step = 0;
5127
5128   // we must somehow check if right button is still down (might be released off board!)
5129   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5130   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5131   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5132   if(!step) return;
5133   lastX = x; lastY = y;
5134
5135   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5136   if(endPV < 0) return;
5137   if(y < margin) step = 1; else
5138   if(y > h - margin) step = -1;
5139   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5140   currentMove += step;
5141   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5142   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5143                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5144   DrawPosition(FALSE, boards[currentMove]);
5145 }
5146
5147
5148 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5149 // All positions will have equal probability, but the current method will not provide a unique
5150 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5151 #define DARK 1
5152 #define LITE 2
5153 #define ANY 3
5154
5155 int squaresLeft[4];
5156 int piecesLeft[(int)BlackPawn];
5157 int seed, nrOfShuffles;
5158
5159 void GetPositionNumber()
5160 {       // sets global variable seed
5161         int i;
5162
5163         seed = appData.defaultFrcPosition;
5164         if(seed < 0) { // randomize based on time for negative FRC position numbers
5165                 for(i=0; i<50; i++) seed += random();
5166                 seed = random() ^ random() >> 8 ^ random() << 8;
5167                 if(seed<0) seed = -seed;
5168         }
5169 }
5170
5171 int put(Board board, int pieceType, int rank, int n, int shade)
5172 // put the piece on the (n-1)-th empty squares of the given shade
5173 {
5174         int i;
5175
5176         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5177                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5178                         board[rank][i] = (ChessSquare) pieceType;
5179                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5180                         squaresLeft[ANY]--;
5181                         piecesLeft[pieceType]--;
5182                         return i;
5183                 }
5184         }
5185         return -1;
5186 }
5187
5188
5189 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5190 // calculate where the next piece goes, (any empty square), and put it there
5191 {
5192         int i;
5193
5194         i = seed % squaresLeft[shade];
5195         nrOfShuffles *= squaresLeft[shade];
5196         seed /= squaresLeft[shade];
5197         put(board, pieceType, rank, i, shade);
5198 }
5199
5200 void AddTwoPieces(Board board, int pieceType, int rank)
5201 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5202 {
5203         int i, n=squaresLeft[ANY], j=n-1, k;
5204
5205         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5206         i = seed % k;  // pick one
5207         nrOfShuffles *= k;
5208         seed /= k;
5209         while(i >= j) i -= j--;
5210         j = n - 1 - j; i += j;
5211         put(board, pieceType, rank, j, ANY);
5212         put(board, pieceType, rank, i, ANY);
5213 }
5214
5215 void SetUpShuffle(Board board, int number)
5216 {
5217         int i, p, first=1;
5218
5219         GetPositionNumber(); nrOfShuffles = 1;
5220
5221         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5222         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5223         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5224
5225         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5226
5227         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5228             p = (int) board[0][i];
5229             if(p < (int) BlackPawn) piecesLeft[p] ++;
5230             board[0][i] = EmptySquare;
5231         }
5232
5233         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5234             // shuffles restricted to allow normal castling put KRR first
5235             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5236                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5237             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5238                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5239             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5240                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5241             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5242                 put(board, WhiteRook, 0, 0, ANY);
5243             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5244         }
5245
5246         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5247             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5248             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5249                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5250                 while(piecesLeft[p] >= 2) {
5251                     AddOnePiece(board, p, 0, LITE);
5252                     AddOnePiece(board, p, 0, DARK);
5253                 }
5254                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5255             }
5256
5257         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5258             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5259             // but we leave King and Rooks for last, to possibly obey FRC restriction
5260             if(p == (int)WhiteRook) continue;
5261             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5262             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5263         }
5264
5265         // now everything is placed, except perhaps King (Unicorn) and Rooks
5266
5267         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5268             // Last King gets castling rights
5269             while(piecesLeft[(int)WhiteUnicorn]) {
5270                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5271                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5272             }
5273
5274             while(piecesLeft[(int)WhiteKing]) {
5275                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5276                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5277             }
5278
5279
5280         } else {
5281             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5282             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5283         }
5284
5285         // Only Rooks can be left; simply place them all
5286         while(piecesLeft[(int)WhiteRook]) {
5287                 i = put(board, WhiteRook, 0, 0, ANY);
5288                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5289                         if(first) {
5290                                 first=0;
5291                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5292                         }
5293                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5294                 }
5295         }
5296         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5297             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5298         }
5299
5300         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5301 }
5302
5303 int SetCharTable( char *table, const char * map )
5304 /* [HGM] moved here from winboard.c because of its general usefulness */
5305 /*       Basically a safe strcpy that uses the last character as King */
5306 {
5307     int result = FALSE; int NrPieces;
5308
5309     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5310                     && NrPieces >= 12 && !(NrPieces&1)) {
5311         int i; /* [HGM] Accept even length from 12 to 34 */
5312
5313         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5314         for( i=0; i<NrPieces/2-1; i++ ) {
5315             table[i] = map[i];
5316             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5317         }
5318         table[(int) WhiteKing]  = map[NrPieces/2-1];
5319         table[(int) BlackKing]  = map[NrPieces-1];
5320
5321         result = TRUE;
5322     }
5323
5324     return result;
5325 }
5326
5327 void Prelude(Board board)
5328 {       // [HGM] superchess: random selection of exo-pieces
5329         int i, j, k; ChessSquare p;
5330         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5331
5332         GetPositionNumber(); // use FRC position number
5333
5334         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5335             SetCharTable(pieceToChar, appData.pieceToCharTable);
5336             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5337                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5338         }
5339
5340         j = seed%4;                 seed /= 4;
5341         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5342         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5343         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5344         j = seed%3 + (seed%3 >= j); seed /= 3;
5345         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5346         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5347         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5348         j = seed%3;                 seed /= 3;
5349         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5350         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5351         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5352         j = seed%2 + (seed%2 >= j); seed /= 2;
5353         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5354         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5355         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5356         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5357         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5358         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5359         put(board, exoPieces[0],    0, 0, ANY);
5360         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5361 }
5362
5363 void
5364 InitPosition(redraw)
5365      int redraw;
5366 {
5367     ChessSquare (* pieces)[BOARD_FILES];
5368     int i, j, pawnRow, overrule,
5369     oldx = gameInfo.boardWidth,
5370     oldy = gameInfo.boardHeight,
5371     oldh = gameInfo.holdingsWidth;
5372     static int oldv;
5373
5374     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5375
5376     /* [AS] Initialize pv info list [HGM] and game status */
5377     {
5378         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5379             pvInfoList[i].depth = 0;
5380             boards[i][EP_STATUS] = EP_NONE;
5381             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5382         }
5383
5384         initialRulePlies = 0; /* 50-move counter start */
5385
5386         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5387         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5388     }
5389
5390
5391     /* [HGM] logic here is completely changed. In stead of full positions */
5392     /* the initialized data only consist of the two backranks. The switch */
5393     /* selects which one we will use, which is than copied to the Board   */
5394     /* initialPosition, which for the rest is initialized by Pawns and    */
5395     /* empty squares. This initial position is then copied to boards[0],  */
5396     /* possibly after shuffling, so that it remains available.            */
5397
5398     gameInfo.holdingsWidth = 0; /* default board sizes */
5399     gameInfo.boardWidth    = 8;
5400     gameInfo.boardHeight   = 8;
5401     gameInfo.holdingsSize  = 0;
5402     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5403     for(i=0; i<BOARD_FILES-2; i++)
5404       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5405     initialPosition[EP_STATUS] = EP_NONE;
5406     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5407     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5408          SetCharTable(pieceNickName, appData.pieceNickNames);
5409     else SetCharTable(pieceNickName, "............");
5410     pieces = FIDEArray;
5411
5412     switch (gameInfo.variant) {
5413     case VariantFischeRandom:
5414       shuffleOpenings = TRUE;
5415     default:
5416       break;
5417     case VariantShatranj:
5418       pieces = ShatranjArray;
5419       nrCastlingRights = 0;
5420       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5421       break;
5422     case VariantMakruk:
5423       pieces = makrukArray;
5424       nrCastlingRights = 0;
5425       startedFromSetupPosition = TRUE;
5426       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5427       break;
5428     case VariantTwoKings:
5429       pieces = twoKingsArray;
5430       break;
5431     case VariantCapaRandom:
5432       shuffleOpenings = TRUE;
5433     case VariantCapablanca:
5434       pieces = CapablancaArray;
5435       gameInfo.boardWidth = 10;
5436       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5437       break;
5438     case VariantGothic:
5439       pieces = GothicArray;
5440       gameInfo.boardWidth = 10;
5441       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5442       break;
5443     case VariantSChess:
5444       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5445       gameInfo.holdingsSize = 7;
5446       break;
5447     case VariantJanus:
5448       pieces = JanusArray;
5449       gameInfo.boardWidth = 10;
5450       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5451       nrCastlingRights = 6;
5452         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5453         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5454         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5455         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5456         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5457         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5458       break;
5459     case VariantFalcon:
5460       pieces = FalconArray;
5461       gameInfo.boardWidth = 10;
5462       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5463       break;
5464     case VariantXiangqi:
5465       pieces = XiangqiArray;
5466       gameInfo.boardWidth  = 9;
5467       gameInfo.boardHeight = 10;
5468       nrCastlingRights = 0;
5469       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5470       break;
5471     case VariantShogi:
5472       pieces = ShogiArray;
5473       gameInfo.boardWidth  = 9;
5474       gameInfo.boardHeight = 9;
5475       gameInfo.holdingsSize = 7;
5476       nrCastlingRights = 0;
5477       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5478       break;
5479     case VariantCourier:
5480       pieces = CourierArray;
5481       gameInfo.boardWidth  = 12;
5482       nrCastlingRights = 0;
5483       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5484       break;
5485     case VariantKnightmate:
5486       pieces = KnightmateArray;
5487       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5488       break;
5489     case VariantSpartan:
5490       pieces = SpartanArray;
5491       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5492       break;
5493     case VariantFairy:
5494       pieces = fairyArray;
5495       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5496       break;
5497     case VariantGreat:
5498       pieces = GreatArray;
5499       gameInfo.boardWidth = 10;
5500       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5501       gameInfo.holdingsSize = 8;
5502       break;
5503     case VariantSuper:
5504       pieces = FIDEArray;
5505       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5506       gameInfo.holdingsSize = 8;
5507       startedFromSetupPosition = TRUE;
5508       break;
5509     case VariantCrazyhouse:
5510     case VariantBughouse:
5511       pieces = FIDEArray;
5512       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5513       gameInfo.holdingsSize = 5;
5514       break;
5515     case VariantWildCastle:
5516       pieces = FIDEArray;
5517       /* !!?shuffle with kings guaranteed to be on d or e file */
5518       shuffleOpenings = 1;
5519       break;
5520     case VariantNoCastle:
5521       pieces = FIDEArray;
5522       nrCastlingRights = 0;
5523       /* !!?unconstrained back-rank shuffle */
5524       shuffleOpenings = 1;
5525       break;
5526     }
5527
5528     overrule = 0;
5529     if(appData.NrFiles >= 0) {
5530         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5531         gameInfo.boardWidth = appData.NrFiles;
5532     }
5533     if(appData.NrRanks >= 0) {
5534         gameInfo.boardHeight = appData.NrRanks;
5535     }
5536     if(appData.holdingsSize >= 0) {
5537         i = appData.holdingsSize;
5538         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5539         gameInfo.holdingsSize = i;
5540     }
5541     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5542     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5543         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5544
5545     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5546     if(pawnRow < 1) pawnRow = 1;
5547     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5548
5549     /* User pieceToChar list overrules defaults */
5550     if(appData.pieceToCharTable != NULL)
5551         SetCharTable(pieceToChar, appData.pieceToCharTable);
5552
5553     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5554
5555         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5556             s = (ChessSquare) 0; /* account holding counts in guard band */
5557         for( i=0; i<BOARD_HEIGHT; i++ )
5558             initialPosition[i][j] = s;
5559
5560         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5561         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5562         initialPosition[pawnRow][j] = WhitePawn;
5563         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5564         if(gameInfo.variant == VariantXiangqi) {
5565             if(j&1) {
5566                 initialPosition[pawnRow][j] =
5567                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5568                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5569                    initialPosition[2][j] = WhiteCannon;
5570                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5571                 }
5572             }
5573         }
5574         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5575     }
5576     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5577
5578             j=BOARD_LEFT+1;
5579             initialPosition[1][j] = WhiteBishop;
5580             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5581             j=BOARD_RGHT-2;
5582             initialPosition[1][j] = WhiteRook;
5583             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5584     }
5585
5586     if( nrCastlingRights == -1) {
5587         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5588         /*       This sets default castling rights from none to normal corners   */
5589         /* Variants with other castling rights must set them themselves above    */
5590         nrCastlingRights = 6;
5591
5592         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5593         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5594         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5595         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5596         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5597         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5598      }
5599
5600      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5601      if(gameInfo.variant == VariantGreat) { // promotion commoners
5602         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5603         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5604         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5605         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5606      }
5607      if( gameInfo.variant == VariantSChess ) {
5608       initialPosition[1][0] = BlackMarshall;
5609       initialPosition[2][0] = BlackAngel;
5610       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5611       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5612       initialPosition[1][1] = initialPosition[2][1] = 
5613       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5614      }
5615   if (appData.debugMode) {
5616     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5617   }
5618     if(shuffleOpenings) {
5619         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5620         startedFromSetupPosition = TRUE;
5621     }
5622     if(startedFromPositionFile) {
5623       /* [HGM] loadPos: use PositionFile for every new game */
5624       CopyBoard(initialPosition, filePosition);
5625       for(i=0; i<nrCastlingRights; i++)
5626           initialRights[i] = filePosition[CASTLING][i];
5627       startedFromSetupPosition = TRUE;
5628     }
5629
5630     CopyBoard(boards[0], initialPosition);
5631
5632     if(oldx != gameInfo.boardWidth ||
5633        oldy != gameInfo.boardHeight ||
5634        oldv != gameInfo.variant ||
5635        oldh != gameInfo.holdingsWidth
5636                                          )
5637             InitDrawingSizes(-2 ,0);
5638
5639     oldv = gameInfo.variant;
5640     if (redraw)
5641       DrawPosition(TRUE, boards[currentMove]);
5642 }
5643
5644 void
5645 SendBoard(cps, moveNum)
5646      ChessProgramState *cps;
5647      int moveNum;
5648 {
5649     char message[MSG_SIZ];
5650
5651     if (cps->useSetboard) {
5652       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5653       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5654       SendToProgram(message, cps);
5655       free(fen);
5656
5657     } else {
5658       ChessSquare *bp;
5659       int i, j;
5660       /* Kludge to set black to move, avoiding the troublesome and now
5661        * deprecated "black" command.
5662        */
5663       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5664         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5665
5666       SendToProgram("edit\n", cps);
5667       SendToProgram("#\n", cps);
5668       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5669         bp = &boards[moveNum][i][BOARD_LEFT];
5670         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5671           if ((int) *bp < (int) BlackPawn) {
5672             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5673                     AAA + j, ONE + i);
5674             if(message[0] == '+' || message[0] == '~') {
5675               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5676                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5677                         AAA + j, ONE + i);
5678             }
5679             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5680                 message[1] = BOARD_RGHT   - 1 - j + '1';
5681                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5682             }
5683             SendToProgram(message, cps);
5684           }
5685         }
5686       }
5687
5688       SendToProgram("c\n", cps);
5689       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5690         bp = &boards[moveNum][i][BOARD_LEFT];
5691         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5692           if (((int) *bp != (int) EmptySquare)
5693               && ((int) *bp >= (int) BlackPawn)) {
5694             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5695                     AAA + j, ONE + i);
5696             if(message[0] == '+' || message[0] == '~') {
5697               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5698                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5699                         AAA + j, ONE + i);
5700             }
5701             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5702                 message[1] = BOARD_RGHT   - 1 - j + '1';
5703                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5704             }
5705             SendToProgram(message, cps);
5706           }
5707         }
5708       }
5709
5710       SendToProgram(".\n", cps);
5711     }
5712     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5713 }
5714
5715 ChessSquare
5716 DefaultPromoChoice(int white)
5717 {
5718     ChessSquare result;
5719     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5720         result = WhiteFerz; // no choice
5721     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5722         result= WhiteKing; // in Suicide Q is the last thing we want
5723     else if(gameInfo.variant == VariantSpartan)
5724         result = white ? WhiteQueen : WhiteAngel;
5725     else result = WhiteQueen;
5726     if(!white) result = WHITE_TO_BLACK result;
5727     return result;
5728 }
5729
5730 static int autoQueen; // [HGM] oneclick
5731
5732 int
5733 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5734 {
5735     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5736     /* [HGM] add Shogi promotions */
5737     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5738     ChessSquare piece;
5739     ChessMove moveType;
5740     Boolean premove;
5741
5742     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5743     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5744
5745     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5746       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5747         return FALSE;
5748
5749     piece = boards[currentMove][fromY][fromX];
5750     if(gameInfo.variant == VariantShogi) {
5751         promotionZoneSize = BOARD_HEIGHT/3;
5752         highestPromotingPiece = (int)WhiteFerz;
5753     } else if(gameInfo.variant == VariantMakruk) {
5754         promotionZoneSize = 3;
5755     }
5756
5757     // Treat Lance as Pawn when it is not representing Amazon
5758     if(gameInfo.variant != VariantSuper) {
5759         if(piece == WhiteLance) piece = WhitePawn; else
5760         if(piece == BlackLance) piece = BlackPawn;
5761     }
5762
5763     // next weed out all moves that do not touch the promotion zone at all
5764     if((int)piece >= BlackPawn) {
5765         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5766              return FALSE;
5767         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5768     } else {
5769         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5770            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5771     }
5772
5773     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5774
5775     // weed out mandatory Shogi promotions
5776     if(gameInfo.variant == VariantShogi) {
5777         if(piece >= BlackPawn) {
5778             if(toY == 0 && piece == BlackPawn ||
5779                toY == 0 && piece == BlackQueen ||
5780                toY <= 1 && piece == BlackKnight) {
5781                 *promoChoice = '+';
5782                 return FALSE;
5783             }
5784         } else {
5785             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5786                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5787                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5788                 *promoChoice = '+';
5789                 return FALSE;
5790             }
5791         }
5792     }
5793
5794     // weed out obviously illegal Pawn moves
5795     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5796         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5797         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5798         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5799         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5800         // note we are not allowed to test for valid (non-)capture, due to premove
5801     }
5802
5803     // we either have a choice what to promote to, or (in Shogi) whether to promote
5804     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5805         *promoChoice = PieceToChar(BlackFerz);  // no choice
5806         return FALSE;
5807     }
5808     // no sense asking what we must promote to if it is going to explode...
5809     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5810         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5811         return FALSE;
5812     }
5813     // give caller the default choice even if we will not make it
5814     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5815     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5816     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5817                            && gameInfo.variant != VariantShogi
5818                            && gameInfo.variant != VariantSuper) return FALSE;
5819     if(autoQueen) return FALSE; // predetermined
5820
5821     // suppress promotion popup on illegal moves that are not premoves
5822     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5823               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5824     if(appData.testLegality && !premove) {
5825         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5826                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5827         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5828             return FALSE;
5829     }
5830
5831     return TRUE;
5832 }
5833
5834 int
5835 InPalace(row, column)
5836      int row, column;
5837 {   /* [HGM] for Xiangqi */
5838     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5839          column < (BOARD_WIDTH + 4)/2 &&
5840          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5841     return FALSE;
5842 }
5843
5844 int
5845 PieceForSquare (x, y)
5846      int x;
5847      int y;
5848 {
5849   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5850      return -1;
5851   else
5852      return boards[currentMove][y][x];
5853 }
5854
5855 int
5856 OKToStartUserMove(x, y)
5857      int x, y;
5858 {
5859     ChessSquare from_piece;
5860     int white_piece;
5861
5862     if (matchMode) return FALSE;
5863     if (gameMode == EditPosition) return TRUE;
5864
5865     if (x >= 0 && y >= 0)
5866       from_piece = boards[currentMove][y][x];
5867     else
5868       from_piece = EmptySquare;
5869
5870     if (from_piece == EmptySquare) return FALSE;
5871
5872     white_piece = (int)from_piece >= (int)WhitePawn &&
5873       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5874
5875     switch (gameMode) {
5876       case PlayFromGameFile:
5877       case AnalyzeFile:
5878       case TwoMachinesPlay:
5879       case EndOfGame:
5880         return FALSE;
5881
5882       case IcsObserving:
5883       case IcsIdle:
5884         return FALSE;
5885
5886       case MachinePlaysWhite:
5887       case IcsPlayingBlack:
5888         if (appData.zippyPlay) return FALSE;
5889         if (white_piece) {
5890             DisplayMoveError(_("You are playing Black"));
5891             return FALSE;
5892         }
5893         break;
5894
5895       case MachinePlaysBlack:
5896       case IcsPlayingWhite:
5897         if (appData.zippyPlay) return FALSE;
5898         if (!white_piece) {
5899             DisplayMoveError(_("You are playing White"));
5900             return FALSE;
5901         }
5902         break;
5903
5904       case EditGame:
5905         if (!white_piece && WhiteOnMove(currentMove)) {
5906             DisplayMoveError(_("It is White's turn"));
5907             return FALSE;
5908         }
5909         if (white_piece && !WhiteOnMove(currentMove)) {
5910             DisplayMoveError(_("It is Black's turn"));
5911             return FALSE;
5912         }
5913         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5914             /* Editing correspondence game history */
5915             /* Could disallow this or prompt for confirmation */
5916             cmailOldMove = -1;
5917         }
5918         break;
5919
5920       case BeginningOfGame:
5921         if (appData.icsActive) return FALSE;
5922         if (!appData.noChessProgram) {
5923             if (!white_piece) {
5924                 DisplayMoveError(_("You are playing White"));
5925                 return FALSE;
5926             }
5927         }
5928         break;
5929
5930       case Training:
5931         if (!white_piece && WhiteOnMove(currentMove)) {
5932             DisplayMoveError(_("It is White's turn"));
5933             return FALSE;
5934         }
5935         if (white_piece && !WhiteOnMove(currentMove)) {
5936             DisplayMoveError(_("It is Black's turn"));
5937             return FALSE;
5938         }
5939         break;
5940
5941       default:
5942       case IcsExamining:
5943         break;
5944     }
5945     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5946         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5947         && gameMode != AnalyzeFile && gameMode != Training) {
5948         DisplayMoveError(_("Displayed position is not current"));
5949         return FALSE;
5950     }
5951     return TRUE;
5952 }
5953
5954 Boolean
5955 OnlyMove(int *x, int *y, Boolean captures) {
5956     DisambiguateClosure cl;
5957     if (appData.zippyPlay) return FALSE;
5958     switch(gameMode) {
5959       case MachinePlaysBlack:
5960       case IcsPlayingWhite:
5961       case BeginningOfGame:
5962         if(!WhiteOnMove(currentMove)) return FALSE;
5963         break;
5964       case MachinePlaysWhite:
5965       case IcsPlayingBlack:
5966         if(WhiteOnMove(currentMove)) return FALSE;
5967         break;
5968       case EditGame:
5969         break;
5970       default:
5971         return FALSE;
5972     }
5973     cl.pieceIn = EmptySquare;
5974     cl.rfIn = *y;
5975     cl.ffIn = *x;
5976     cl.rtIn = -1;
5977     cl.ftIn = -1;
5978     cl.promoCharIn = NULLCHAR;
5979     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5980     if( cl.kind == NormalMove ||
5981         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5982         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5983         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5984       fromX = cl.ff;
5985       fromY = cl.rf;
5986       *x = cl.ft;
5987       *y = cl.rt;
5988       return TRUE;
5989     }
5990     if(cl.kind != ImpossibleMove) return FALSE;
5991     cl.pieceIn = EmptySquare;
5992     cl.rfIn = -1;
5993     cl.ffIn = -1;
5994     cl.rtIn = *y;
5995     cl.ftIn = *x;
5996     cl.promoCharIn = NULLCHAR;
5997     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5998     if( cl.kind == NormalMove ||
5999         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6000         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6001         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6002       fromX = cl.ff;
6003       fromY = cl.rf;
6004       *x = cl.ft;
6005       *y = cl.rt;
6006       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6007       return TRUE;
6008     }
6009     return FALSE;
6010 }
6011
6012 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6013 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6014 int lastLoadGameUseList = FALSE;
6015 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6016 ChessMove lastLoadGameStart = EndOfFile;
6017
6018 void
6019 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6020      int fromX, fromY, toX, toY;
6021      int promoChar;
6022 {
6023     ChessMove moveType;
6024     ChessSquare pdown, pup;
6025
6026     /* Check if the user is playing in turn.  This is complicated because we
6027        let the user "pick up" a piece before it is his turn.  So the piece he
6028        tried to pick up may have been captured by the time he puts it down!
6029        Therefore we use the color the user is supposed to be playing in this
6030        test, not the color of the piece that is currently on the starting
6031        square---except in EditGame mode, where the user is playing both
6032        sides; fortunately there the capture race can't happen.  (It can
6033        now happen in IcsExamining mode, but that's just too bad.  The user
6034        will get a somewhat confusing message in that case.)
6035        */
6036
6037     switch (gameMode) {
6038       case PlayFromGameFile:
6039       case AnalyzeFile:
6040       case TwoMachinesPlay:
6041       case EndOfGame:
6042       case IcsObserving:
6043       case IcsIdle:
6044         /* We switched into a game mode where moves are not accepted,
6045            perhaps while the mouse button was down. */
6046         return;
6047
6048       case MachinePlaysWhite:
6049         /* User is moving for Black */
6050         if (WhiteOnMove(currentMove)) {
6051             DisplayMoveError(_("It is White's turn"));
6052             return;
6053         }
6054         break;
6055
6056       case MachinePlaysBlack:
6057         /* User is moving for White */
6058         if (!WhiteOnMove(currentMove)) {
6059             DisplayMoveError(_("It is Black's turn"));
6060             return;
6061         }
6062         break;
6063
6064       case EditGame:
6065       case IcsExamining:
6066       case BeginningOfGame:
6067       case AnalyzeMode:
6068       case Training:
6069         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6070         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6071             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6072             /* User is moving for Black */
6073             if (WhiteOnMove(currentMove)) {
6074                 DisplayMoveError(_("It is White's turn"));
6075                 return;
6076             }
6077         } else {
6078             /* User is moving for White */
6079             if (!WhiteOnMove(currentMove)) {
6080                 DisplayMoveError(_("It is Black's turn"));
6081                 return;
6082             }
6083         }
6084         break;
6085
6086       case IcsPlayingBlack:
6087         /* User is moving for Black */
6088         if (WhiteOnMove(currentMove)) {
6089             if (!appData.premove) {
6090                 DisplayMoveError(_("It is White's turn"));
6091             } else if (toX >= 0 && toY >= 0) {
6092                 premoveToX = toX;
6093                 premoveToY = toY;
6094                 premoveFromX = fromX;
6095                 premoveFromY = fromY;
6096                 premovePromoChar = promoChar;
6097                 gotPremove = 1;
6098                 if (appData.debugMode)
6099                     fprintf(debugFP, "Got premove: fromX %d,"
6100                             "fromY %d, toX %d, toY %d\n",
6101                             fromX, fromY, toX, toY);
6102             }
6103             return;
6104         }
6105         break;
6106
6107       case IcsPlayingWhite:
6108         /* User is moving for White */
6109         if (!WhiteOnMove(currentMove)) {
6110             if (!appData.premove) {
6111                 DisplayMoveError(_("It is Black's turn"));
6112             } else if (toX >= 0 && toY >= 0) {
6113                 premoveToX = toX;
6114                 premoveToY = toY;
6115                 premoveFromX = fromX;
6116                 premoveFromY = fromY;
6117                 premovePromoChar = promoChar;
6118                 gotPremove = 1;
6119                 if (appData.debugMode)
6120                     fprintf(debugFP, "Got premove: fromX %d,"
6121                             "fromY %d, toX %d, toY %d\n",
6122                             fromX, fromY, toX, toY);
6123             }
6124             return;
6125         }
6126         break;
6127
6128       default:
6129         break;
6130
6131       case EditPosition:
6132         /* EditPosition, empty square, or different color piece;
6133            click-click move is possible */
6134         if (toX == -2 || toY == -2) {
6135             boards[0][fromY][fromX] = EmptySquare;
6136             DrawPosition(FALSE, boards[currentMove]);
6137             return;
6138         } else if (toX >= 0 && toY >= 0) {
6139             boards[0][toY][toX] = boards[0][fromY][fromX];
6140             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6141                 if(boards[0][fromY][0] != EmptySquare) {
6142                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6143                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6144                 }
6145             } else
6146             if(fromX == BOARD_RGHT+1) {
6147                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6148                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6149                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6150                 }
6151             } else
6152             boards[0][fromY][fromX] = EmptySquare;
6153             DrawPosition(FALSE, boards[currentMove]);
6154             return;
6155         }
6156         return;
6157     }
6158
6159     if(toX < 0 || toY < 0) return;
6160     pdown = boards[currentMove][fromY][fromX];
6161     pup = boards[currentMove][toY][toX];
6162
6163     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6164     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6165          if( pup != EmptySquare ) return;
6166          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6167            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6168                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6169            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6170            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6171            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6172            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6173          fromY = DROP_RANK;
6174     }
6175
6176     /* [HGM] always test for legality, to get promotion info */
6177     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6178                                          fromY, fromX, toY, toX, promoChar);
6179     /* [HGM] but possibly ignore an IllegalMove result */
6180     if (appData.testLegality) {
6181         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6182             DisplayMoveError(_("Illegal move"));
6183             return;
6184         }
6185     }
6186
6187     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6188 }
6189
6190 /* Common tail of UserMoveEvent and DropMenuEvent */
6191 int
6192 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6193      ChessMove moveType;
6194      int fromX, fromY, toX, toY;
6195      /*char*/int promoChar;
6196 {
6197     char *bookHit = 0;
6198
6199     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6200         // [HGM] superchess: suppress promotions to non-available piece
6201         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6202         if(WhiteOnMove(currentMove)) {
6203             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6204         } else {
6205             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6206         }
6207     }
6208
6209     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6210        move type in caller when we know the move is a legal promotion */
6211     if(moveType == NormalMove && promoChar)
6212         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6213
6214     /* [HGM] <popupFix> The following if has been moved here from
6215        UserMoveEvent(). Because it seemed to belong here (why not allow
6216        piece drops in training games?), and because it can only be
6217        performed after it is known to what we promote. */
6218     if (gameMode == Training) {
6219       /* compare the move played on the board to the next move in the
6220        * game. If they match, display the move and the opponent's response.
6221        * If they don't match, display an error message.
6222        */
6223       int saveAnimate;
6224       Board testBoard;
6225       CopyBoard(testBoard, boards[currentMove]);
6226       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6227
6228       if (CompareBoards(testBoard, boards[currentMove+1])) {
6229         ForwardInner(currentMove+1);
6230
6231         /* Autoplay the opponent's response.
6232          * if appData.animate was TRUE when Training mode was entered,
6233          * the response will be animated.
6234          */
6235         saveAnimate = appData.animate;
6236         appData.animate = animateTraining;
6237         ForwardInner(currentMove+1);
6238         appData.animate = saveAnimate;
6239
6240         /* check for the end of the game */
6241         if (currentMove >= forwardMostMove) {
6242           gameMode = PlayFromGameFile;
6243           ModeHighlight();
6244           SetTrainingModeOff();
6245           DisplayInformation(_("End of game"));
6246         }
6247       } else {
6248         DisplayError(_("Incorrect move"), 0);
6249       }
6250       return 1;
6251     }
6252
6253   /* Ok, now we know that the move is good, so we can kill
6254      the previous line in Analysis Mode */
6255   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6256                                 && currentMove < forwardMostMove) {
6257     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6258     else forwardMostMove = currentMove;
6259   }
6260
6261   /* If we need the chess program but it's dead, restart it */
6262   ResurrectChessProgram();
6263
6264   /* A user move restarts a paused game*/
6265   if (pausing)
6266     PauseEvent();
6267
6268   thinkOutput[0] = NULLCHAR;
6269
6270   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6271
6272   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6273     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6274     return 1;
6275   }
6276
6277   if (gameMode == BeginningOfGame) {
6278     if (appData.noChessProgram) {
6279       gameMode = EditGame;
6280       SetGameInfo();
6281     } else {
6282       char buf[MSG_SIZ];
6283       gameMode = MachinePlaysBlack;
6284       StartClocks();
6285       SetGameInfo();
6286       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6287       DisplayTitle(buf);
6288       if (first.sendName) {
6289         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6290         SendToProgram(buf, &first);
6291       }
6292       StartClocks();
6293     }
6294     ModeHighlight();
6295   }
6296
6297   /* Relay move to ICS or chess engine */
6298   if (appData.icsActive) {
6299     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6300         gameMode == IcsExamining) {
6301       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6302         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6303         SendToICS("draw ");
6304         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6305       }
6306       // also send plain move, in case ICS does not understand atomic claims
6307       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6308       ics_user_moved = 1;
6309     }
6310   } else {
6311     if (first.sendTime && (gameMode == BeginningOfGame ||
6312                            gameMode == MachinePlaysWhite ||
6313                            gameMode == MachinePlaysBlack)) {
6314       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6315     }
6316     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6317          // [HGM] book: if program might be playing, let it use book
6318         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6319         first.maybeThinking = TRUE;
6320     } else SendMoveToProgram(forwardMostMove-1, &first);
6321     if (currentMove == cmailOldMove + 1) {
6322       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6323     }
6324   }
6325
6326   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6327
6328   switch (gameMode) {
6329   case EditGame:
6330     if(appData.testLegality)
6331     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6332     case MT_NONE:
6333     case MT_CHECK:
6334       break;
6335     case MT_CHECKMATE:
6336     case MT_STAINMATE:
6337       if (WhiteOnMove(currentMove)) {
6338         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6339       } else {
6340         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6341       }
6342       break;
6343     case MT_STALEMATE:
6344       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6345       break;
6346     }
6347     break;
6348
6349   case MachinePlaysBlack:
6350   case MachinePlaysWhite:
6351     /* disable certain menu options while machine is thinking */
6352     SetMachineThinkingEnables();
6353     break;
6354
6355   default:
6356     break;
6357   }
6358
6359   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6360   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6361
6362   if(bookHit) { // [HGM] book: simulate book reply
6363         static char bookMove[MSG_SIZ]; // a bit generous?
6364
6365         programStats.nodes = programStats.depth = programStats.time =
6366         programStats.score = programStats.got_only_move = 0;
6367         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6368
6369         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6370         strcat(bookMove, bookHit);
6371         HandleMachineMove(bookMove, &first);
6372   }
6373   return 1;
6374 }
6375
6376 void
6377 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6378      Board board;
6379      int flags;
6380      ChessMove kind;
6381      int rf, ff, rt, ft;
6382      VOIDSTAR closure;
6383 {
6384     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6385     Markers *m = (Markers *) closure;
6386     if(rf == fromY && ff == fromX)
6387         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6388                          || kind == WhiteCapturesEnPassant
6389                          || kind == BlackCapturesEnPassant);
6390     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6391 }
6392
6393 void
6394 MarkTargetSquares(int clear)
6395 {
6396   int x, y;
6397   if(!appData.markers || !appData.highlightDragging ||
6398      !appData.testLegality || gameMode == EditPosition) return;
6399   if(clear) {
6400     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6401   } else {
6402     int capt = 0;
6403     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6404     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6405       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6406       if(capt)
6407       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6408     }
6409   }
6410   DrawPosition(TRUE, NULL);
6411 }
6412
6413 int
6414 Explode(Board board, int fromX, int fromY, int toX, int toY)
6415 {
6416     if(gameInfo.variant == VariantAtomic &&
6417        (board[toY][toX] != EmptySquare ||                     // capture?
6418         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6419                          board[fromY][fromX] == BlackPawn   )
6420       )) {
6421         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6422         return TRUE;
6423     }
6424     return FALSE;
6425 }
6426
6427 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6428
6429 int CanPromote(ChessSquare piece, int y)
6430 {
6431         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6432         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6433         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6434            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6435            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6436                                                   gameInfo.variant == VariantMakruk) return FALSE;
6437         return (piece == BlackPawn && y == 1 ||
6438                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6439                 piece == BlackLance && y == 1 ||
6440                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6441 }
6442
6443 void LeftClick(ClickType clickType, int xPix, int yPix)
6444 {
6445     int x, y;
6446     Boolean saveAnimate;
6447     static int second = 0, promotionChoice = 0, clearFlag = 0;
6448     char promoChoice = NULLCHAR;
6449     ChessSquare piece;
6450
6451     if(appData.seekGraph && appData.icsActive && loggedOn &&
6452         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6453         SeekGraphClick(clickType, xPix, yPix, 0);
6454         return;
6455     }
6456
6457     if (clickType == Press) ErrorPopDown();
6458     MarkTargetSquares(1);
6459
6460     x = EventToSquare(xPix, BOARD_WIDTH);
6461     y = EventToSquare(yPix, BOARD_HEIGHT);
6462     if (!flipView && y >= 0) {
6463         y = BOARD_HEIGHT - 1 - y;
6464     }
6465     if (flipView && x >= 0) {
6466         x = BOARD_WIDTH - 1 - x;
6467     }
6468
6469     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6470         defaultPromoChoice = promoSweep;
6471         promoSweep = EmptySquare;   // terminate sweep
6472         promoDefaultAltered = TRUE;
6473         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6474     }
6475
6476     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6477         if(clickType == Release) return; // ignore upclick of click-click destination
6478         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6479         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6480         if(gameInfo.holdingsWidth &&
6481                 (WhiteOnMove(currentMove)
6482                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6483                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6484             // click in right holdings, for determining promotion piece
6485             ChessSquare p = boards[currentMove][y][x];
6486             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6487             if(p != EmptySquare) {
6488                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6489                 fromX = fromY = -1;
6490                 return;
6491             }
6492         }
6493         DrawPosition(FALSE, boards[currentMove]);
6494         return;
6495     }
6496
6497     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6498     if(clickType == Press
6499             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6500               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6501               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6502         return;
6503
6504     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6505         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6506
6507     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6508         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6509                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6510         defaultPromoChoice = DefaultPromoChoice(side);
6511     }
6512
6513     autoQueen = appData.alwaysPromoteToQueen;
6514
6515     if (fromX == -1) {
6516       int originalY = y;
6517       gatingPiece = EmptySquare;
6518       if (clickType != Press) {
6519         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6520             DragPieceEnd(xPix, yPix); dragging = 0;
6521             DrawPosition(FALSE, NULL);
6522         }
6523         return;
6524       }
6525       fromX = x; fromY = y;
6526       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6527          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6528          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6529             /* First square */
6530             if (OKToStartUserMove(fromX, fromY)) {
6531                 second = 0;
6532                 MarkTargetSquares(0);
6533                 DragPieceBegin(xPix, yPix); dragging = 1;
6534                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6535                     promoSweep = defaultPromoChoice;
6536                     selectFlag = 0; lastX = xPix; lastY = yPix;
6537                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6538                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6539                 }
6540                 if (appData.highlightDragging) {
6541                     SetHighlights(fromX, fromY, -1, -1);
6542                 }
6543             } else fromX = fromY = -1;
6544             return;
6545         }
6546     }
6547
6548     /* fromX != -1 */
6549     if (clickType == Press && gameMode != EditPosition) {
6550         ChessSquare fromP;
6551         ChessSquare toP;
6552         int frc;
6553
6554         // ignore off-board to clicks
6555         if(y < 0 || x < 0) return;
6556
6557         /* Check if clicking again on the same color piece */
6558         fromP = boards[currentMove][fromY][fromX];
6559         toP = boards[currentMove][y][x];
6560         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6561         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6562              WhitePawn <= toP && toP <= WhiteKing &&
6563              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6564              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6565             (BlackPawn <= fromP && fromP <= BlackKing &&
6566              BlackPawn <= toP && toP <= BlackKing &&
6567              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6568              !(fromP == BlackKing && toP == BlackRook && frc))) {
6569             /* Clicked again on same color piece -- changed his mind */
6570             second = (x == fromX && y == fromY);
6571             promoDefaultAltered = FALSE;
6572            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6573             if (appData.highlightDragging) {
6574                 SetHighlights(x, y, -1, -1);
6575             } else {
6576                 ClearHighlights();
6577             }
6578             if (OKToStartUserMove(x, y)) {
6579                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6580                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6581                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6582                  gatingPiece = boards[currentMove][fromY][fromX];
6583                 else gatingPiece = EmptySquare;
6584                 fromX = x;
6585                 fromY = y; dragging = 1;
6586                 MarkTargetSquares(0);
6587                 DragPieceBegin(xPix, yPix);
6588                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6589                     promoSweep = defaultPromoChoice;
6590                     selectFlag = 0; lastX = xPix; lastY = yPix;
6591                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6592                 }
6593             }
6594            }
6595            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6596            second = FALSE; 
6597         }
6598         // ignore clicks on holdings
6599         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6600     }
6601
6602     if (clickType == Release && x == fromX && y == fromY) {
6603         DragPieceEnd(xPix, yPix); dragging = 0;
6604         if(clearFlag) {
6605             // a deferred attempt to click-click move an empty square on top of a piece
6606             boards[currentMove][y][x] = EmptySquare;
6607             ClearHighlights();
6608             DrawPosition(FALSE, boards[currentMove]);
6609             fromX = fromY = -1; clearFlag = 0;
6610             return;
6611         }
6612         if (appData.animateDragging) {
6613             /* Undo animation damage if any */
6614             DrawPosition(FALSE, NULL);
6615         }
6616         if (second) {
6617             /* Second up/down in same square; just abort move */
6618             second = 0;
6619             fromX = fromY = -1;
6620             gatingPiece = EmptySquare;
6621             ClearHighlights();
6622             gotPremove = 0;
6623             ClearPremoveHighlights();
6624         } else {
6625             /* First upclick in same square; start click-click mode */
6626             SetHighlights(x, y, -1, -1);
6627         }
6628         return;
6629     }
6630
6631     clearFlag = 0;
6632
6633     /* we now have a different from- and (possibly off-board) to-square */
6634     /* Completed move */
6635     toX = x;
6636     toY = y;
6637     saveAnimate = appData.animate;
6638     if (clickType == Press) {
6639         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6640             // must be Edit Position mode with empty-square selected
6641             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6642             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6643             return;
6644         }
6645         /* Finish clickclick move */
6646         if (appData.animate || appData.highlightLastMove) {
6647             SetHighlights(fromX, fromY, toX, toY);
6648         } else {
6649             ClearHighlights();
6650         }
6651     } else {
6652         /* Finish drag move */
6653         if (appData.highlightLastMove) {
6654             SetHighlights(fromX, fromY, toX, toY);
6655         } else {
6656             ClearHighlights();
6657         }
6658         DragPieceEnd(xPix, yPix); dragging = 0;
6659         /* Don't animate move and drag both */
6660         appData.animate = FALSE;
6661     }
6662
6663     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6664     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6665         ChessSquare piece = boards[currentMove][fromY][fromX];
6666         if(gameMode == EditPosition && piece != EmptySquare &&
6667            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6668             int n;
6669
6670             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6671                 n = PieceToNumber(piece - (int)BlackPawn);
6672                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6673                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6674                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6675             } else
6676             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6677                 n = PieceToNumber(piece);
6678                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6679                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6680                 boards[currentMove][n][BOARD_WIDTH-2]++;
6681             }
6682             boards[currentMove][fromY][fromX] = EmptySquare;
6683         }
6684         ClearHighlights();
6685         fromX = fromY = -1;
6686         DrawPosition(TRUE, boards[currentMove]);
6687         return;
6688     }
6689
6690     // off-board moves should not be highlighted
6691     if(x < 0 || y < 0) ClearHighlights();
6692
6693     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6694
6695     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6696         SetHighlights(fromX, fromY, toX, toY);
6697         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6698             // [HGM] super: promotion to captured piece selected from holdings
6699             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6700             promotionChoice = TRUE;
6701             // kludge follows to temporarily execute move on display, without promoting yet
6702             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6703             boards[currentMove][toY][toX] = p;
6704             DrawPosition(FALSE, boards[currentMove]);
6705             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6706             boards[currentMove][toY][toX] = q;
6707             DisplayMessage("Click in holdings to choose piece", "");
6708             return;
6709         }
6710         PromotionPopUp();
6711     } else {
6712         int oldMove = currentMove;
6713         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6714         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6715         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6716         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6717            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6718             DrawPosition(TRUE, boards[currentMove]);
6719         fromX = fromY = -1;
6720     }
6721     appData.animate = saveAnimate;
6722     if (appData.animate || appData.animateDragging) {
6723         /* Undo animation damage if needed */
6724         DrawPosition(FALSE, NULL);
6725     }
6726 }
6727
6728 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6729 {   // front-end-free part taken out of PieceMenuPopup
6730     int whichMenu; int xSqr, ySqr;
6731
6732     if(seekGraphUp) { // [HGM] seekgraph
6733         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6734         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6735         return -2;
6736     }
6737
6738     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6739          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6740         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6741         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6742         if(action == Press)   {
6743             originalFlip = flipView;
6744             flipView = !flipView; // temporarily flip board to see game from partners perspective
6745             DrawPosition(TRUE, partnerBoard);
6746             DisplayMessage(partnerStatus, "");
6747             partnerUp = TRUE;
6748         } else if(action == Release) {
6749             flipView = originalFlip;
6750             DrawPosition(TRUE, boards[currentMove]);
6751             partnerUp = FALSE;
6752         }
6753         return -2;
6754     }
6755
6756     xSqr = EventToSquare(x, BOARD_WIDTH);
6757     ySqr = EventToSquare(y, BOARD_HEIGHT);
6758     if (action == Release) {
6759         if(pieceSweep != EmptySquare) {
6760             EditPositionMenuEvent(pieceSweep, toX, toY);
6761             pieceSweep = EmptySquare;
6762         } else UnLoadPV(); // [HGM] pv
6763     }
6764     if (action != Press) return -2; // return code to be ignored
6765     switch (gameMode) {
6766       case IcsExamining:
6767         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6768       case EditPosition:
6769         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6770         if (xSqr < 0 || ySqr < 0) return -1;
6771         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6772         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6773         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6774         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6775         NextPiece(0);
6776         return -2;\r
6777       case IcsObserving:
6778         if(!appData.icsEngineAnalyze) return -1;
6779       case IcsPlayingWhite:
6780       case IcsPlayingBlack:
6781         if(!appData.zippyPlay) goto noZip;
6782       case AnalyzeMode:
6783       case AnalyzeFile:
6784       case MachinePlaysWhite:
6785       case MachinePlaysBlack:
6786       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6787         if (!appData.dropMenu) {
6788           LoadPV(x, y);
6789           return 2; // flag front-end to grab mouse events
6790         }
6791         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6792            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6793       case EditGame:
6794       noZip:
6795         if (xSqr < 0 || ySqr < 0) return -1;
6796         if (!appData.dropMenu || appData.testLegality &&
6797             gameInfo.variant != VariantBughouse &&
6798             gameInfo.variant != VariantCrazyhouse) return -1;
6799         whichMenu = 1; // drop menu
6800         break;
6801       default:
6802         return -1;
6803     }
6804
6805     if (((*fromX = xSqr) < 0) ||
6806         ((*fromY = ySqr) < 0)) {
6807         *fromX = *fromY = -1;
6808         return -1;
6809     }
6810     if (flipView)
6811       *fromX = BOARD_WIDTH - 1 - *fromX;
6812     else
6813       *fromY = BOARD_HEIGHT - 1 - *fromY;
6814
6815     return whichMenu;
6816 }
6817
6818 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6819 {
6820 //    char * hint = lastHint;
6821     FrontEndProgramStats stats;
6822
6823     stats.which = cps == &first ? 0 : 1;
6824     stats.depth = cpstats->depth;
6825     stats.nodes = cpstats->nodes;
6826     stats.score = cpstats->score;
6827     stats.time = cpstats->time;
6828     stats.pv = cpstats->movelist;
6829     stats.hint = lastHint;
6830     stats.an_move_index = 0;
6831     stats.an_move_count = 0;
6832
6833     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6834         stats.hint = cpstats->move_name;
6835         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6836         stats.an_move_count = cpstats->nr_moves;
6837     }
6838
6839     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
6840
6841     SetProgramStats( &stats );
6842 }
6843
6844 void
6845 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6846 {       // count all piece types
6847         int p, f, r;
6848         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6849         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6850         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6851                 p = board[r][f];
6852                 pCnt[p]++;
6853                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6854                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6855                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6856                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6857                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6858                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6859         }
6860 }
6861
6862 int
6863 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6864 {
6865         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6866         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6867
6868         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6869         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6870         if(myPawns == 2 && nMine == 3) // KPP
6871             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6872         if(myPawns == 1 && nMine == 2) // KP
6873             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6874         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6875             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6876         if(myPawns) return FALSE;
6877         if(pCnt[WhiteRook+side])
6878             return pCnt[BlackRook-side] ||
6879                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6880                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6881                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6882         if(pCnt[WhiteCannon+side]) {
6883             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6884             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6885         }
6886         if(pCnt[WhiteKnight+side])
6887             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6888         return FALSE;
6889 }
6890
6891 int
6892 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6893 {
6894         VariantClass v = gameInfo.variant;
6895
6896         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6897         if(v == VariantShatranj) return TRUE; // always winnable through baring
6898         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6899         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6900
6901         if(v == VariantXiangqi) {
6902                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6903
6904                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6905                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6906                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6907                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6908                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6909                 if(stale) // we have at least one last-rank P plus perhaps C
6910                     return majors // KPKX
6911                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6912                 else // KCA*E*
6913                     return pCnt[WhiteFerz+side] // KCAK
6914                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6915                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6916                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6917
6918         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6919                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6920
6921                 if(nMine == 1) return FALSE; // bare King
6922                 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
6923                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6924                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6925                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6926                 if(pCnt[WhiteKnight+side])
6927                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6928                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6929                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6930                 if(nBishops)
6931                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6932                 if(pCnt[WhiteAlfil+side])
6933                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6934                 if(pCnt[WhiteWazir+side])
6935                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6936         }
6937
6938         return TRUE;
6939 }
6940
6941 int
6942 Adjudicate(ChessProgramState *cps)
6943 {       // [HGM] some adjudications useful with buggy engines
6944         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6945         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6946         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6947         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6948         int k, count = 0; static int bare = 1;
6949         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6950         Boolean canAdjudicate = !appData.icsActive;
6951
6952         // most tests only when we understand the game, i.e. legality-checking on
6953             if( appData.testLegality )
6954             {   /* [HGM] Some more adjudications for obstinate engines */
6955                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6956                 static int moveCount = 6;
6957                 ChessMove result;
6958                 char *reason = NULL;
6959
6960                 /* Count what is on board. */
6961                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6962
6963                 /* Some material-based adjudications that have to be made before stalemate test */
6964                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6965                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6966                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6967                      if(canAdjudicate && appData.checkMates) {
6968                          if(engineOpponent)
6969                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6970                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6971                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6972                          return 1;
6973                      }
6974                 }
6975
6976                 /* Bare King in Shatranj (loses) or Losers (wins) */
6977                 if( nrW == 1 || nrB == 1) {
6978                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6979                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6980                      if(canAdjudicate && appData.checkMates) {
6981                          if(engineOpponent)
6982                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6983                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6984                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6985                          return 1;
6986                      }
6987                   } else
6988                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6989                   {    /* bare King */
6990                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6991                         if(canAdjudicate && appData.checkMates) {
6992                             /* but only adjudicate if adjudication enabled */
6993                             if(engineOpponent)
6994                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6995                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6996                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6997                             return 1;
6998                         }
6999                   }
7000                 } else bare = 1;
7001
7002
7003             // don't wait for engine to announce game end if we can judge ourselves
7004             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7005               case MT_CHECK:
7006                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7007                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7008                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7009                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7010                             checkCnt++;
7011                         if(checkCnt >= 2) {
7012                             reason = "Xboard adjudication: 3rd check";
7013                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7014                             break;
7015                         }
7016                     }
7017                 }
7018               case MT_NONE:
7019               default:
7020                 break;
7021               case MT_STALEMATE:
7022               case MT_STAINMATE:
7023                 reason = "Xboard adjudication: Stalemate";
7024                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7025                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7026                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7027                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7028                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7029                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7030                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7031                                                                         EP_CHECKMATE : EP_WINS);
7032                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7033                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7034                 }
7035                 break;
7036               case MT_CHECKMATE:
7037                 reason = "Xboard adjudication: Checkmate";
7038                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7039                 break;
7040             }
7041
7042                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7043                     case EP_STALEMATE:
7044                         result = GameIsDrawn; break;
7045                     case EP_CHECKMATE:
7046                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7047                     case EP_WINS:
7048                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7049                     default:
7050                         result = EndOfFile;
7051                 }
7052                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7053                     if(engineOpponent)
7054                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7055                     GameEnds( result, reason, GE_XBOARD );
7056                     return 1;
7057                 }
7058
7059                 /* Next absolutely insufficient mating material. */
7060                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7061                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7062                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7063
7064                      /* always flag draws, for judging claims */
7065                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7066
7067                      if(canAdjudicate && appData.materialDraws) {
7068                          /* but only adjudicate them if adjudication enabled */
7069                          if(engineOpponent) {
7070                            SendToProgram("force\n", engineOpponent); // suppress reply
7071                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7072                          }
7073                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7074                          return 1;
7075                      }
7076                 }
7077
7078                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7079                 if(gameInfo.variant == VariantXiangqi ?
7080                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7081                  : nrW + nrB == 4 &&
7082                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7083                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7084                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7085                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7086                    ) ) {
7087                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7088                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7089                           if(engineOpponent) {
7090                             SendToProgram("force\n", engineOpponent); // suppress reply
7091                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7092                           }
7093                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7094                           return 1;
7095                      }
7096                 } else moveCount = 6;
7097             }
7098         if (appData.debugMode) { int i;
7099             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7100                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7101                     appData.drawRepeats);
7102             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7103               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7104
7105         }
7106
7107         // Repetition draws and 50-move rule can be applied independently of legality testing
7108
7109                 /* Check for rep-draws */
7110                 count = 0;
7111                 for(k = forwardMostMove-2;
7112                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7113                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7114                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7115                     k-=2)
7116                 {   int rights=0;
7117                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7118                         /* compare castling rights */
7119                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7120                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7121                                 rights++; /* King lost rights, while rook still had them */
7122                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7123                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7124                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7125                                    rights++; /* but at least one rook lost them */
7126                         }
7127                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7128                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7129                                 rights++;
7130                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7131                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7132                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7133                                    rights++;
7134                         }
7135                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7136                             && appData.drawRepeats > 1) {
7137                              /* adjudicate after user-specified nr of repeats */
7138                              int result = GameIsDrawn;
7139                              char *details = "XBoard adjudication: repetition draw";
7140                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7141                                 // [HGM] xiangqi: check for forbidden perpetuals
7142                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7143                                 for(m=forwardMostMove; m>k; m-=2) {
7144                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7145                                         ourPerpetual = 0; // the current mover did not always check
7146                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7147                                         hisPerpetual = 0; // the opponent did not always check
7148                                 }
7149                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7150                                                                         ourPerpetual, hisPerpetual);
7151                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7152                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7153                                     details = "Xboard adjudication: perpetual checking";
7154                                 } else
7155                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7156                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7157                                 } else
7158                                 // Now check for perpetual chases
7159                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7160                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7161                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7162                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7163                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7164                                         details = "Xboard adjudication: perpetual chasing";
7165                                     } else
7166                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7167                                         break; // Abort repetition-checking loop.
7168                                 }
7169                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7170                              }
7171                              if(engineOpponent) {
7172                                SendToProgram("force\n", engineOpponent); // suppress reply
7173                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7174                              }
7175                              GameEnds( result, details, GE_XBOARD );
7176                              return 1;
7177                         }
7178                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7179                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7180                     }
7181                 }
7182
7183                 /* Now we test for 50-move draws. Determine ply count */
7184                 count = forwardMostMove;
7185                 /* look for last irreversble move */
7186                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7187                     count--;
7188                 /* if we hit starting position, add initial plies */
7189                 if( count == backwardMostMove )
7190                     count -= initialRulePlies;
7191                 count = forwardMostMove - count;
7192                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7193                         // adjust reversible move counter for checks in Xiangqi
7194                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7195                         if(i < backwardMostMove) i = backwardMostMove;
7196                         while(i <= forwardMostMove) {
7197                                 lastCheck = inCheck; // check evasion does not count
7198                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7199                                 if(inCheck || lastCheck) count--; // check does not count
7200                                 i++;
7201                         }
7202                 }
7203                 if( count >= 100)
7204                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7205                          /* this is used to judge if draw claims are legal */
7206                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7207                          if(engineOpponent) {
7208                            SendToProgram("force\n", engineOpponent); // suppress reply
7209                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7210                          }
7211                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7212                          return 1;
7213                 }
7214
7215                 /* if draw offer is pending, treat it as a draw claim
7216                  * when draw condition present, to allow engines a way to
7217                  * claim draws before making their move to avoid a race
7218                  * condition occurring after their move
7219                  */
7220                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7221                          char *p = NULL;
7222                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7223                              p = "Draw claim: 50-move rule";
7224                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7225                              p = "Draw claim: 3-fold repetition";
7226                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7227                              p = "Draw claim: insufficient mating material";
7228                          if( p != NULL && canAdjudicate) {
7229                              if(engineOpponent) {
7230                                SendToProgram("force\n", engineOpponent); // suppress reply
7231                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7232                              }
7233                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7234                              return 1;
7235                          }
7236                 }
7237
7238                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7239                     if(engineOpponent) {
7240                       SendToProgram("force\n", engineOpponent); // suppress reply
7241                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7242                     }
7243                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7244                     return 1;
7245                 }
7246         return 0;
7247 }
7248
7249 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7250 {   // [HGM] book: this routine intercepts moves to simulate book replies
7251     char *bookHit = NULL;
7252
7253     //first determine if the incoming move brings opponent into his book
7254     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7255         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7256     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7257     if(bookHit != NULL && !cps->bookSuspend) {
7258         // make sure opponent is not going to reply after receiving move to book position
7259         SendToProgram("force\n", cps);
7260         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7261     }
7262     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7263     // now arrange restart after book miss
7264     if(bookHit) {
7265         // after a book hit we never send 'go', and the code after the call to this routine
7266         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7267         char buf[MSG_SIZ];
7268         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7269         SendToProgram(buf, cps);
7270         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7271     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7272         SendToProgram("go\n", cps);
7273         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7274     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7275         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7276             SendToProgram("go\n", cps);
7277         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7278     }
7279     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7280 }
7281
7282 char *savedMessage;
7283 ChessProgramState *savedState;
7284 void DeferredBookMove(void)
7285 {
7286         if(savedState->lastPing != savedState->lastPong)
7287                     ScheduleDelayedEvent(DeferredBookMove, 10);
7288         else
7289         HandleMachineMove(savedMessage, savedState);
7290 }
7291
7292 void
7293 HandleMachineMove(message, cps)
7294      char *message;
7295      ChessProgramState *cps;
7296 {
7297     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7298     char realname[MSG_SIZ];
7299     int fromX, fromY, toX, toY;
7300     ChessMove moveType;
7301     char promoChar;
7302     char *p;
7303     int machineWhite;
7304     char *bookHit;
7305
7306     cps->userError = 0;
7307
7308 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7309     /*
7310      * Kludge to ignore BEL characters
7311      */
7312     while (*message == '\007') message++;
7313
7314     /*
7315      * [HGM] engine debug message: ignore lines starting with '#' character
7316      */
7317     if(cps->debug && *message == '#') return;
7318
7319     /*
7320      * Look for book output
7321      */
7322     if (cps == &first && bookRequested) {
7323         if (message[0] == '\t' || message[0] == ' ') {
7324             /* Part of the book output is here; append it */
7325             strcat(bookOutput, message);
7326             strcat(bookOutput, "  \n");
7327             return;
7328         } else if (bookOutput[0] != NULLCHAR) {
7329             /* All of book output has arrived; display it */
7330             char *p = bookOutput;
7331             while (*p != NULLCHAR) {
7332                 if (*p == '\t') *p = ' ';
7333                 p++;
7334             }
7335             DisplayInformation(bookOutput);
7336             bookRequested = FALSE;
7337             /* Fall through to parse the current output */
7338         }
7339     }
7340
7341     /*
7342      * Look for machine move.
7343      */
7344     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7345         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7346     {
7347         /* This method is only useful on engines that support ping */
7348         if (cps->lastPing != cps->lastPong) {
7349           if (gameMode == BeginningOfGame) {
7350             /* Extra move from before last new; ignore */
7351             if (appData.debugMode) {
7352                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7353             }
7354           } else {
7355             if (appData.debugMode) {
7356                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7357                         cps->which, gameMode);
7358             }
7359
7360             SendToProgram("undo\n", cps);
7361           }
7362           return;
7363         }
7364
7365         switch (gameMode) {
7366           case BeginningOfGame:
7367             /* Extra move from before last reset; ignore */
7368             if (appData.debugMode) {
7369                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7370             }
7371             return;
7372
7373           case EndOfGame:
7374           case IcsIdle:
7375           default:
7376             /* Extra move after we tried to stop.  The mode test is
7377                not a reliable way of detecting this problem, but it's
7378                the best we can do on engines that don't support ping.
7379             */
7380             if (appData.debugMode) {
7381                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7382                         cps->which, gameMode);
7383             }
7384             SendToProgram("undo\n", cps);
7385             return;
7386
7387           case MachinePlaysWhite:
7388           case IcsPlayingWhite:
7389             machineWhite = TRUE;
7390             break;
7391
7392           case MachinePlaysBlack:
7393           case IcsPlayingBlack:
7394             machineWhite = FALSE;
7395             break;
7396
7397           case TwoMachinesPlay:
7398             machineWhite = (cps->twoMachinesColor[0] == 'w');
7399             break;
7400         }
7401         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7402             if (appData.debugMode) {
7403                 fprintf(debugFP,
7404                         "Ignoring move out of turn by %s, gameMode %d"
7405                         ", forwardMost %d\n",
7406                         cps->which, gameMode, forwardMostMove);
7407             }
7408             return;
7409         }
7410
7411     if (appData.debugMode) { int f = forwardMostMove;
7412         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7413                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7414                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7415     }
7416         if(cps->alphaRank) AlphaRank(machineMove, 4);
7417         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7418                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7419             /* Machine move could not be parsed; ignore it. */
7420           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7421                     machineMove, _(cps->which));
7422             DisplayError(buf1, 0);
7423             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7424                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7425             if (gameMode == TwoMachinesPlay) {
7426               GameEnds(machineWhite ? BlackWins : WhiteWins,
7427                        buf1, GE_XBOARD);
7428             }
7429             return;
7430         }
7431
7432         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7433         /* So we have to redo legality test with true e.p. status here,  */
7434         /* to make sure an illegal e.p. capture does not slip through,   */
7435         /* to cause a forfeit on a justified illegal-move complaint      */
7436         /* of the opponent.                                              */
7437         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7438            ChessMove moveType;
7439            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7440                              fromY, fromX, toY, toX, promoChar);
7441             if (appData.debugMode) {
7442                 int i;
7443                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7444                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7445                 fprintf(debugFP, "castling rights\n");
7446             }
7447             if(moveType == IllegalMove) {
7448               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7449                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7450                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7451                            buf1, GE_XBOARD);
7452                 return;
7453            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7454            /* [HGM] Kludge to handle engines that send FRC-style castling
7455               when they shouldn't (like TSCP-Gothic) */
7456            switch(moveType) {
7457              case WhiteASideCastleFR:
7458              case BlackASideCastleFR:
7459                toX+=2;
7460                currentMoveString[2]++;
7461                break;
7462              case WhiteHSideCastleFR:
7463              case BlackHSideCastleFR:
7464                toX--;
7465                currentMoveString[2]--;
7466                break;
7467              default: ; // nothing to do, but suppresses warning of pedantic compilers
7468            }
7469         }
7470         hintRequested = FALSE;
7471         lastHint[0] = NULLCHAR;
7472         bookRequested = FALSE;
7473         /* Program may be pondering now */
7474         cps->maybeThinking = TRUE;
7475         if (cps->sendTime == 2) cps->sendTime = 1;
7476         if (cps->offeredDraw) cps->offeredDraw--;
7477
7478         /* [AS] Save move info*/
7479         pvInfoList[ forwardMostMove ].score = programStats.score;
7480         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7481         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7482
7483         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7484
7485         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7486         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7487             int count = 0;
7488
7489             while( count < adjudicateLossPlies ) {
7490                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7491
7492                 if( count & 1 ) {
7493                     score = -score; /* Flip score for winning side */
7494                 }
7495
7496                 if( score > adjudicateLossThreshold ) {
7497                     break;
7498                 }
7499
7500                 count++;
7501             }
7502
7503             if( count >= adjudicateLossPlies ) {
7504                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7505
7506                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7507                     "Xboard adjudication",
7508                     GE_XBOARD );
7509
7510                 return;
7511             }
7512         }
7513
7514         if(Adjudicate(cps)) {
7515             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7516             return; // [HGM] adjudicate: for all automatic game ends
7517         }
7518
7519 #if ZIPPY
7520         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7521             first.initDone) {
7522           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7523                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7524                 SendToICS("draw ");
7525                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7526           }
7527           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7528           ics_user_moved = 1;
7529           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7530                 char buf[3*MSG_SIZ];
7531
7532                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7533                         programStats.score / 100.,
7534                         programStats.depth,
7535                         programStats.time / 100.,
7536                         (unsigned int)programStats.nodes,
7537                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7538                         programStats.movelist);
7539                 SendToICS(buf);
7540 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7541           }
7542         }
7543 #endif
7544
7545         /* [AS] Clear stats for next move */
7546         ClearProgramStats();
7547         thinkOutput[0] = NULLCHAR;
7548         hiddenThinkOutputState = 0;
7549
7550         bookHit = NULL;
7551         if (gameMode == TwoMachinesPlay) {
7552             /* [HGM] relaying draw offers moved to after reception of move */
7553             /* and interpreting offer as claim if it brings draw condition */
7554             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7555                 SendToProgram("draw\n", cps->other);
7556             }
7557             if (cps->other->sendTime) {
7558                 SendTimeRemaining(cps->other,
7559                                   cps->other->twoMachinesColor[0] == 'w');
7560             }
7561             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7562             if (firstMove && !bookHit) {
7563                 firstMove = FALSE;
7564                 if (cps->other->useColors) {
7565                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7566                 }
7567                 SendToProgram("go\n", cps->other);
7568             }
7569             cps->other->maybeThinking = TRUE;
7570         }
7571
7572         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7573
7574         if (!pausing && appData.ringBellAfterMoves) {
7575             RingBell();
7576         }
7577
7578         /*
7579          * Reenable menu items that were disabled while
7580          * machine was thinking
7581          */
7582         if (gameMode != TwoMachinesPlay)
7583             SetUserThinkingEnables();
7584
7585         // [HGM] book: after book hit opponent has received move and is now in force mode
7586         // force the book reply into it, and then fake that it outputted this move by jumping
7587         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7588         if(bookHit) {
7589                 static char bookMove[MSG_SIZ]; // a bit generous?
7590
7591                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7592                 strcat(bookMove, bookHit);
7593                 message = bookMove;
7594                 cps = cps->other;
7595                 programStats.nodes = programStats.depth = programStats.time =
7596                 programStats.score = programStats.got_only_move = 0;
7597                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7598
7599                 if(cps->lastPing != cps->lastPong) {
7600                     savedMessage = message; // args for deferred call
7601                     savedState = cps;
7602                     ScheduleDelayedEvent(DeferredBookMove, 10);
7603                     return;
7604                 }
7605                 goto FakeBookMove;
7606         }
7607
7608         return;
7609     }
7610
7611     /* Set special modes for chess engines.  Later something general
7612      *  could be added here; for now there is just one kludge feature,
7613      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7614      *  when "xboard" is given as an interactive command.
7615      */
7616     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7617         cps->useSigint = FALSE;
7618         cps->useSigterm = FALSE;
7619     }
7620     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7621       ParseFeatures(message+8, cps);
7622       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7623     }
7624
7625     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7626       int dummy, s=6; char buf[MSG_SIZ];
7627       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7628       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7629       ParseFEN(boards[0], &dummy, message+s);
7630       DrawPosition(TRUE, boards[0]);
7631       startedFromSetupPosition = TRUE;
7632       return;
7633     }
7634     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7635      * want this, I was asked to put it in, and obliged.
7636      */
7637     if (!strncmp(message, "setboard ", 9)) {
7638         Board initial_position;
7639
7640         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7641
7642         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7643             DisplayError(_("Bad FEN received from engine"), 0);
7644             return ;
7645         } else {
7646            Reset(TRUE, FALSE);
7647            CopyBoard(boards[0], initial_position);
7648            initialRulePlies = FENrulePlies;
7649            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7650            else gameMode = MachinePlaysBlack;
7651            DrawPosition(FALSE, boards[currentMove]);
7652         }
7653         return;
7654     }
7655
7656     /*
7657      * Look for communication commands
7658      */
7659     if (!strncmp(message, "telluser ", 9)) {
7660         if(message[9] == '\\' && message[10] == '\\')
7661             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7662         DisplayNote(message + 9);
7663         return;
7664     }
7665     if (!strncmp(message, "tellusererror ", 14)) {
7666         cps->userError = 1;
7667         if(message[14] == '\\' && message[15] == '\\')
7668             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7669         DisplayError(message + 14, 0);
7670         return;
7671     }
7672     if (!strncmp(message, "tellopponent ", 13)) {
7673       if (appData.icsActive) {
7674         if (loggedOn) {
7675           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7676           SendToICS(buf1);
7677         }
7678       } else {
7679         DisplayNote(message + 13);
7680       }
7681       return;
7682     }
7683     if (!strncmp(message, "tellothers ", 11)) {
7684       if (appData.icsActive) {
7685         if (loggedOn) {
7686           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7687           SendToICS(buf1);
7688         }
7689       }
7690       return;
7691     }
7692     if (!strncmp(message, "tellall ", 8)) {
7693       if (appData.icsActive) {
7694         if (loggedOn) {
7695           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7696           SendToICS(buf1);
7697         }
7698       } else {
7699         DisplayNote(message + 8);
7700       }
7701       return;
7702     }
7703     if (strncmp(message, "warning", 7) == 0) {
7704         /* Undocumented feature, use tellusererror in new code */
7705         DisplayError(message, 0);
7706         return;
7707     }
7708     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7709         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7710         strcat(realname, " query");
7711         AskQuestion(realname, buf2, buf1, cps->pr);
7712         return;
7713     }
7714     /* Commands from the engine directly to ICS.  We don't allow these to be
7715      *  sent until we are logged on. Crafty kibitzes have been known to
7716      *  interfere with the login process.
7717      */
7718     if (loggedOn) {
7719         if (!strncmp(message, "tellics ", 8)) {
7720             SendToICS(message + 8);
7721             SendToICS("\n");
7722             return;
7723         }
7724         if (!strncmp(message, "tellicsnoalias ", 15)) {
7725             SendToICS(ics_prefix);
7726             SendToICS(message + 15);
7727             SendToICS("\n");
7728             return;
7729         }
7730         /* The following are for backward compatibility only */
7731         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7732             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7733             SendToICS(ics_prefix);
7734             SendToICS(message);
7735             SendToICS("\n");
7736             return;
7737         }
7738     }
7739     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7740         return;
7741     }
7742     /*
7743      * If the move is illegal, cancel it and redraw the board.
7744      * Also deal with other error cases.  Matching is rather loose
7745      * here to accommodate engines written before the spec.
7746      */
7747     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7748         strncmp(message, "Error", 5) == 0) {
7749         if (StrStr(message, "name") ||
7750             StrStr(message, "rating") || StrStr(message, "?") ||
7751             StrStr(message, "result") || StrStr(message, "board") ||
7752             StrStr(message, "bk") || StrStr(message, "computer") ||
7753             StrStr(message, "variant") || StrStr(message, "hint") ||
7754             StrStr(message, "random") || StrStr(message, "depth") ||
7755             StrStr(message, "accepted")) {
7756             return;
7757         }
7758         if (StrStr(message, "protover")) {
7759           /* Program is responding to input, so it's apparently done
7760              initializing, and this error message indicates it is
7761              protocol version 1.  So we don't need to wait any longer
7762              for it to initialize and send feature commands. */
7763           FeatureDone(cps, 1);
7764           cps->protocolVersion = 1;
7765           return;
7766         }
7767         cps->maybeThinking = FALSE;
7768
7769         if (StrStr(message, "draw")) {
7770             /* Program doesn't have "draw" command */
7771             cps->sendDrawOffers = 0;
7772             return;
7773         }
7774         if (cps->sendTime != 1 &&
7775             (StrStr(message, "time") || StrStr(message, "otim"))) {
7776           /* Program apparently doesn't have "time" or "otim" command */
7777           cps->sendTime = 0;
7778           return;
7779         }
7780         if (StrStr(message, "analyze")) {
7781             cps->analysisSupport = FALSE;
7782             cps->analyzing = FALSE;
7783             Reset(FALSE, TRUE);
7784             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7785             DisplayError(buf2, 0);
7786             return;
7787         }
7788         if (StrStr(message, "(no matching move)st")) {
7789           /* Special kludge for GNU Chess 4 only */
7790           cps->stKludge = TRUE;
7791           SendTimeControl(cps, movesPerSession, timeControl,
7792                           timeIncrement, appData.searchDepth,
7793                           searchTime);
7794           return;
7795         }
7796         if (StrStr(message, "(no matching move)sd")) {
7797           /* Special kludge for GNU Chess 4 only */
7798           cps->sdKludge = TRUE;
7799           SendTimeControl(cps, movesPerSession, timeControl,
7800                           timeIncrement, appData.searchDepth,
7801                           searchTime);
7802           return;
7803         }
7804         if (!StrStr(message, "llegal")) {
7805             return;
7806         }
7807         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7808             gameMode == IcsIdle) return;
7809         if (forwardMostMove <= backwardMostMove) return;
7810         if (pausing) PauseEvent();
7811       if(appData.forceIllegal) {
7812             // [HGM] illegal: machine refused move; force position after move into it
7813           SendToProgram("force\n", cps);
7814           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7815                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7816                 // when black is to move, while there might be nothing on a2 or black
7817                 // might already have the move. So send the board as if white has the move.
7818                 // But first we must change the stm of the engine, as it refused the last move
7819                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7820                 if(WhiteOnMove(forwardMostMove)) {
7821                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7822                     SendBoard(cps, forwardMostMove); // kludgeless board
7823                 } else {
7824                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7825                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7826                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7827                 }
7828           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7829             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7830                  gameMode == TwoMachinesPlay)
7831               SendToProgram("go\n", cps);
7832             return;
7833       } else
7834         if (gameMode == PlayFromGameFile) {
7835             /* Stop reading this game file */
7836             gameMode = EditGame;
7837             ModeHighlight();
7838         }
7839         /* [HGM] illegal-move claim should forfeit game when Xboard */
7840         /* only passes fully legal moves                            */
7841         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7842             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7843                                 "False illegal-move claim", GE_XBOARD );
7844             return; // do not take back move we tested as valid
7845         }
7846         currentMove = forwardMostMove-1;
7847         DisplayMove(currentMove-1); /* before DisplayMoveError */
7848         SwitchClocks(forwardMostMove-1); // [HGM] race
7849         DisplayBothClocks();
7850         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7851                 parseList[currentMove], _(cps->which));
7852         DisplayMoveError(buf1);
7853         DrawPosition(FALSE, boards[currentMove]);
7854         return;
7855     }
7856     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7857         /* Program has a broken "time" command that
7858            outputs a string not ending in newline.
7859            Don't use it. */
7860         cps->sendTime = 0;
7861     }
7862
7863     /*
7864      * If chess program startup fails, exit with an error message.
7865      * Attempts to recover here are futile.
7866      */
7867     if ((StrStr(message, "unknown host") != NULL)
7868         || (StrStr(message, "No remote directory") != NULL)
7869         || (StrStr(message, "not found") != NULL)
7870         || (StrStr(message, "No such file") != NULL)
7871         || (StrStr(message, "can't alloc") != NULL)
7872         || (StrStr(message, "Permission denied") != NULL)) {
7873
7874         cps->maybeThinking = FALSE;
7875         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7876                 _(cps->which), cps->program, cps->host, message);
7877         RemoveInputSource(cps->isr);
7878         DisplayFatalError(buf1, 0, 1);
7879         return;
7880     }
7881
7882     /*
7883      * Look for hint output
7884      */
7885     if (sscanf(message, "Hint: %s", buf1) == 1) {
7886         if (cps == &first && hintRequested) {
7887             hintRequested = FALSE;
7888             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7889                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7890                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7891                                     PosFlags(forwardMostMove),
7892                                     fromY, fromX, toY, toX, promoChar, buf1);
7893                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7894                 DisplayInformation(buf2);
7895             } else {
7896                 /* Hint move could not be parsed!? */
7897               snprintf(buf2, sizeof(buf2),
7898                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7899                         buf1, _(cps->which));
7900                 DisplayError(buf2, 0);
7901             }
7902         } else {
7903           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7904         }
7905         return;
7906     }
7907
7908     /*
7909      * Ignore other messages if game is not in progress
7910      */
7911     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7912         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7913
7914     /*
7915      * look for win, lose, draw, or draw offer
7916      */
7917     if (strncmp(message, "1-0", 3) == 0) {
7918         char *p, *q, *r = "";
7919         p = strchr(message, '{');
7920         if (p) {
7921             q = strchr(p, '}');
7922             if (q) {
7923                 *q = NULLCHAR;
7924                 r = p + 1;
7925             }
7926         }
7927         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7928         return;
7929     } else if (strncmp(message, "0-1", 3) == 0) {
7930         char *p, *q, *r = "";
7931         p = strchr(message, '{');
7932         if (p) {
7933             q = strchr(p, '}');
7934             if (q) {
7935                 *q = NULLCHAR;
7936                 r = p + 1;
7937             }
7938         }
7939         /* Kludge for Arasan 4.1 bug */
7940         if (strcmp(r, "Black resigns") == 0) {
7941             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7942             return;
7943         }
7944         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7945         return;
7946     } else if (strncmp(message, "1/2", 3) == 0) {
7947         char *p, *q, *r = "";
7948         p = strchr(message, '{');
7949         if (p) {
7950             q = strchr(p, '}');
7951             if (q) {
7952                 *q = NULLCHAR;
7953                 r = p + 1;
7954             }
7955         }
7956
7957         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7958         return;
7959
7960     } else if (strncmp(message, "White resign", 12) == 0) {
7961         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7962         return;
7963     } else if (strncmp(message, "Black resign", 12) == 0) {
7964         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7965         return;
7966     } else if (strncmp(message, "White matches", 13) == 0 ||
7967                strncmp(message, "Black matches", 13) == 0   ) {
7968         /* [HGM] ignore GNUShogi noises */
7969         return;
7970     } else if (strncmp(message, "White", 5) == 0 &&
7971                message[5] != '(' &&
7972                StrStr(message, "Black") == NULL) {
7973         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7974         return;
7975     } else if (strncmp(message, "Black", 5) == 0 &&
7976                message[5] != '(') {
7977         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7978         return;
7979     } else if (strcmp(message, "resign") == 0 ||
7980                strcmp(message, "computer resigns") == 0) {
7981         switch (gameMode) {
7982           case MachinePlaysBlack:
7983           case IcsPlayingBlack:
7984             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7985             break;
7986           case MachinePlaysWhite:
7987           case IcsPlayingWhite:
7988             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7989             break;
7990           case TwoMachinesPlay:
7991             if (cps->twoMachinesColor[0] == 'w')
7992               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7993             else
7994               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7995             break;
7996           default:
7997             /* can't happen */
7998             break;
7999         }
8000         return;
8001     } else if (strncmp(message, "opponent mates", 14) == 0) {
8002         switch (gameMode) {
8003           case MachinePlaysBlack:
8004           case IcsPlayingBlack:
8005             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8006             break;
8007           case MachinePlaysWhite:
8008           case IcsPlayingWhite:
8009             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8010             break;
8011           case TwoMachinesPlay:
8012             if (cps->twoMachinesColor[0] == 'w')
8013               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8014             else
8015               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8016             break;
8017           default:
8018             /* can't happen */
8019             break;
8020         }
8021         return;
8022     } else if (strncmp(message, "computer mates", 14) == 0) {
8023         switch (gameMode) {
8024           case MachinePlaysBlack:
8025           case IcsPlayingBlack:
8026             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8027             break;
8028           case MachinePlaysWhite:
8029           case IcsPlayingWhite:
8030             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8031             break;
8032           case TwoMachinesPlay:
8033             if (cps->twoMachinesColor[0] == 'w')
8034               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8035             else
8036               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8037             break;
8038           default:
8039             /* can't happen */
8040             break;
8041         }
8042         return;
8043     } else if (strncmp(message, "checkmate", 9) == 0) {
8044         if (WhiteOnMove(forwardMostMove)) {
8045             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8046         } else {
8047             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8048         }
8049         return;
8050     } else if (strstr(message, "Draw") != NULL ||
8051                strstr(message, "game is a draw") != NULL) {
8052         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8053         return;
8054     } else if (strstr(message, "offer") != NULL &&
8055                strstr(message, "draw") != NULL) {
8056 #if ZIPPY
8057         if (appData.zippyPlay && first.initDone) {
8058             /* Relay offer to ICS */
8059             SendToICS(ics_prefix);
8060             SendToICS("draw\n");
8061         }
8062 #endif
8063         cps->offeredDraw = 2; /* valid until this engine moves twice */
8064         if (gameMode == TwoMachinesPlay) {
8065             if (cps->other->offeredDraw) {
8066                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8067             /* [HGM] in two-machine mode we delay relaying draw offer      */
8068             /* until after we also have move, to see if it is really claim */
8069             }
8070         } else if (gameMode == MachinePlaysWhite ||
8071                    gameMode == MachinePlaysBlack) {
8072           if (userOfferedDraw) {
8073             DisplayInformation(_("Machine accepts your draw offer"));
8074             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8075           } else {
8076             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8077           }
8078         }
8079     }
8080
8081
8082     /*
8083      * Look for thinking output
8084      */
8085     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8086           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8087                                 ) {
8088         int plylev, mvleft, mvtot, curscore, time;
8089         char mvname[MOVE_LEN];
8090         u64 nodes; // [DM]
8091         char plyext;
8092         int ignore = FALSE;
8093         int prefixHint = FALSE;
8094         mvname[0] = NULLCHAR;
8095
8096         switch (gameMode) {
8097           case MachinePlaysBlack:
8098           case IcsPlayingBlack:
8099             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8100             break;
8101           case MachinePlaysWhite:
8102           case IcsPlayingWhite:
8103             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8104             break;
8105           case AnalyzeMode:
8106           case AnalyzeFile:
8107             break;
8108           case IcsObserving: /* [DM] icsEngineAnalyze */
8109             if (!appData.icsEngineAnalyze) ignore = TRUE;
8110             break;
8111           case TwoMachinesPlay:
8112             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8113                 ignore = TRUE;
8114             }
8115             break;
8116           default:
8117             ignore = TRUE;
8118             break;
8119         }
8120
8121         if (!ignore) {
8122             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8123             buf1[0] = NULLCHAR;
8124             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8125                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8126
8127                 if (plyext != ' ' && plyext != '\t') {
8128                     time *= 100;
8129                 }
8130
8131                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8132                 if( cps->scoreIsAbsolute &&
8133                     ( gameMode == MachinePlaysBlack ||
8134                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8135                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8136                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8137                      !WhiteOnMove(currentMove)
8138                     ) )
8139                 {
8140                     curscore = -curscore;
8141                 }
8142
8143
8144                 tempStats.depth = plylev;
8145                 tempStats.nodes = nodes;
8146                 tempStats.time = time;
8147                 tempStats.score = curscore;
8148                 tempStats.got_only_move = 0;
8149
8150                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8151                         int ticklen;
8152
8153                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8154                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8155                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8156                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8157                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8158                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8159                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8160                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8161                 }
8162
8163                 /* Buffer overflow protection */
8164                 if (buf1[0] != NULLCHAR) {
8165                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8166                         && appData.debugMode) {
8167                         fprintf(debugFP,
8168                                 "PV is too long; using the first %u bytes.\n",
8169                                 (unsigned) sizeof(tempStats.movelist) - 1);
8170                     }
8171
8172                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8173                 } else {
8174                     sprintf(tempStats.movelist, " no PV\n");
8175                 }
8176
8177                 if (tempStats.seen_stat) {
8178                     tempStats.ok_to_send = 1;
8179                 }
8180
8181                 if (strchr(tempStats.movelist, '(') != NULL) {
8182                     tempStats.line_is_book = 1;
8183                     tempStats.nr_moves = 0;
8184                     tempStats.moves_left = 0;
8185                 } else {
8186                     tempStats.line_is_book = 0;
8187                 }
8188
8189                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8190                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8191
8192                 SendProgramStatsToFrontend( cps, &tempStats );
8193
8194                 /*
8195                     [AS] Protect the thinkOutput buffer from overflow... this
8196                     is only useful if buf1 hasn't overflowed first!
8197                 */
8198                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8199                          plylev,
8200                          (gameMode == TwoMachinesPlay ?
8201                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8202                          ((double) curscore) / 100.0,
8203                          prefixHint ? lastHint : "",
8204                          prefixHint ? " " : "" );
8205
8206                 if( buf1[0] != NULLCHAR ) {
8207                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8208
8209                     if( strlen(buf1) > max_len ) {
8210                         if( appData.debugMode) {
8211                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8212                         }
8213                         buf1[max_len+1] = '\0';
8214                     }
8215
8216                     strcat( thinkOutput, buf1 );
8217                 }
8218
8219                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8220                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8221                     DisplayMove(currentMove - 1);
8222                 }
8223                 return;
8224
8225             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8226                 /* crafty (9.25+) says "(only move) <move>"
8227                  * if there is only 1 legal move
8228                  */
8229                 sscanf(p, "(only move) %s", buf1);
8230                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8231                 sprintf(programStats.movelist, "%s (only move)", buf1);
8232                 programStats.depth = 1;
8233                 programStats.nr_moves = 1;
8234                 programStats.moves_left = 1;
8235                 programStats.nodes = 1;
8236                 programStats.time = 1;
8237                 programStats.got_only_move = 1;
8238
8239                 /* Not really, but we also use this member to
8240                    mean "line isn't going to change" (Crafty
8241                    isn't searching, so stats won't change) */
8242                 programStats.line_is_book = 1;
8243
8244                 SendProgramStatsToFrontend( cps, &programStats );
8245
8246                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8247                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8248                     DisplayMove(currentMove - 1);
8249                 }
8250                 return;
8251             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8252                               &time, &nodes, &plylev, &mvleft,
8253                               &mvtot, mvname) >= 5) {
8254                 /* The stat01: line is from Crafty (9.29+) in response
8255                    to the "." command */
8256                 programStats.seen_stat = 1;
8257                 cps->maybeThinking = TRUE;
8258
8259                 if (programStats.got_only_move || !appData.periodicUpdates)
8260                   return;
8261
8262                 programStats.depth = plylev;
8263                 programStats.time = time;
8264                 programStats.nodes = nodes;
8265                 programStats.moves_left = mvleft;
8266                 programStats.nr_moves = mvtot;
8267                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8268                 programStats.ok_to_send = 1;
8269                 programStats.movelist[0] = '\0';
8270
8271                 SendProgramStatsToFrontend( cps, &programStats );
8272
8273                 return;
8274
8275             } else if (strncmp(message,"++",2) == 0) {
8276                 /* Crafty 9.29+ outputs this */
8277                 programStats.got_fail = 2;
8278                 return;
8279
8280             } else if (strncmp(message,"--",2) == 0) {
8281                 /* Crafty 9.29+ outputs this */
8282                 programStats.got_fail = 1;
8283                 return;
8284
8285             } else if (thinkOutput[0] != NULLCHAR &&
8286                        strncmp(message, "    ", 4) == 0) {
8287                 unsigned message_len;
8288
8289                 p = message;
8290                 while (*p && *p == ' ') p++;
8291
8292                 message_len = strlen( p );
8293
8294                 /* [AS] Avoid buffer overflow */
8295                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8296                     strcat(thinkOutput, " ");
8297                     strcat(thinkOutput, p);
8298                 }
8299
8300                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8301                     strcat(programStats.movelist, " ");
8302                     strcat(programStats.movelist, p);
8303                 }
8304
8305                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8306                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8307                     DisplayMove(currentMove - 1);
8308                 }
8309                 return;
8310             }
8311         }
8312         else {
8313             buf1[0] = NULLCHAR;
8314
8315             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8316                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8317             {
8318                 ChessProgramStats cpstats;
8319
8320                 if (plyext != ' ' && plyext != '\t') {
8321                     time *= 100;
8322                 }
8323
8324                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8325                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8326                     curscore = -curscore;
8327                 }
8328
8329                 cpstats.depth = plylev;
8330                 cpstats.nodes = nodes;
8331                 cpstats.time = time;
8332                 cpstats.score = curscore;
8333                 cpstats.got_only_move = 0;
8334                 cpstats.movelist[0] = '\0';
8335
8336                 if (buf1[0] != NULLCHAR) {
8337                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8338                 }
8339
8340                 cpstats.ok_to_send = 0;
8341                 cpstats.line_is_book = 0;
8342                 cpstats.nr_moves = 0;
8343                 cpstats.moves_left = 0;
8344
8345                 SendProgramStatsToFrontend( cps, &cpstats );
8346             }
8347         }
8348     }
8349 }
8350
8351
8352 /* Parse a game score from the character string "game", and
8353    record it as the history of the current game.  The game
8354    score is NOT assumed to start from the standard position.
8355    The display is not updated in any way.
8356    */
8357 void
8358 ParseGameHistory(game)
8359      char *game;
8360 {
8361     ChessMove moveType;
8362     int fromX, fromY, toX, toY, boardIndex;
8363     char promoChar;
8364     char *p, *q;
8365     char buf[MSG_SIZ];
8366
8367     if (appData.debugMode)
8368       fprintf(debugFP, "Parsing game history: %s\n", game);
8369
8370     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8371     gameInfo.site = StrSave(appData.icsHost);
8372     gameInfo.date = PGNDate();
8373     gameInfo.round = StrSave("-");
8374
8375     /* Parse out names of players */
8376     while (*game == ' ') game++;
8377     p = buf;
8378     while (*game != ' ') *p++ = *game++;
8379     *p = NULLCHAR;
8380     gameInfo.white = StrSave(buf);
8381     while (*game == ' ') game++;
8382     p = buf;
8383     while (*game != ' ' && *game != '\n') *p++ = *game++;
8384     *p = NULLCHAR;
8385     gameInfo.black = StrSave(buf);
8386
8387     /* Parse moves */
8388     boardIndex = blackPlaysFirst ? 1 : 0;
8389     yynewstr(game);
8390     for (;;) {
8391         yyboardindex = boardIndex;
8392         moveType = (ChessMove) Myylex();
8393         switch (moveType) {
8394           case IllegalMove:             /* maybe suicide chess, etc. */
8395   if (appData.debugMode) {
8396     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8397     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8398     setbuf(debugFP, NULL);
8399   }
8400           case WhitePromotion:
8401           case BlackPromotion:
8402           case WhiteNonPromotion:
8403           case BlackNonPromotion:
8404           case NormalMove:
8405           case WhiteCapturesEnPassant:
8406           case BlackCapturesEnPassant:
8407           case WhiteKingSideCastle:
8408           case WhiteQueenSideCastle:
8409           case BlackKingSideCastle:
8410           case BlackQueenSideCastle:
8411           case WhiteKingSideCastleWild:
8412           case WhiteQueenSideCastleWild:
8413           case BlackKingSideCastleWild:
8414           case BlackQueenSideCastleWild:
8415           /* PUSH Fabien */
8416           case WhiteHSideCastleFR:
8417           case WhiteASideCastleFR:
8418           case BlackHSideCastleFR:
8419           case BlackASideCastleFR:
8420           /* POP Fabien */
8421             fromX = currentMoveString[0] - AAA;
8422             fromY = currentMoveString[1] - ONE;
8423             toX = currentMoveString[2] - AAA;
8424             toY = currentMoveString[3] - ONE;
8425             promoChar = currentMoveString[4];
8426             break;
8427           case WhiteDrop:
8428           case BlackDrop:
8429             fromX = moveType == WhiteDrop ?
8430               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8431             (int) CharToPiece(ToLower(currentMoveString[0]));
8432             fromY = DROP_RANK;
8433             toX = currentMoveString[2] - AAA;
8434             toY = currentMoveString[3] - ONE;
8435             promoChar = NULLCHAR;
8436             break;
8437           case AmbiguousMove:
8438             /* bug? */
8439             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8440   if (appData.debugMode) {
8441     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8442     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8443     setbuf(debugFP, NULL);
8444   }
8445             DisplayError(buf, 0);
8446             return;
8447           case ImpossibleMove:
8448             /* bug? */
8449             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8450   if (appData.debugMode) {
8451     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8452     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8453     setbuf(debugFP, NULL);
8454   }
8455             DisplayError(buf, 0);
8456             return;
8457           case EndOfFile:
8458             if (boardIndex < backwardMostMove) {
8459                 /* Oops, gap.  How did that happen? */
8460                 DisplayError(_("Gap in move list"), 0);
8461                 return;
8462             }
8463             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8464             if (boardIndex > forwardMostMove) {
8465                 forwardMostMove = boardIndex;
8466             }
8467             return;
8468           case ElapsedTime:
8469             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8470                 strcat(parseList[boardIndex-1], " ");
8471                 strcat(parseList[boardIndex-1], yy_text);
8472             }
8473             continue;
8474           case Comment:
8475           case PGNTag:
8476           case NAG:
8477           default:
8478             /* ignore */
8479             continue;
8480           case WhiteWins:
8481           case BlackWins:
8482           case GameIsDrawn:
8483           case GameUnfinished:
8484             if (gameMode == IcsExamining) {
8485                 if (boardIndex < backwardMostMove) {
8486                     /* Oops, gap.  How did that happen? */
8487                     return;
8488                 }
8489                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8490                 return;
8491             }
8492             gameInfo.result = moveType;
8493             p = strchr(yy_text, '{');
8494             if (p == NULL) p = strchr(yy_text, '(');
8495             if (p == NULL) {
8496                 p = yy_text;
8497                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8498             } else {
8499                 q = strchr(p, *p == '{' ? '}' : ')');
8500                 if (q != NULL) *q = NULLCHAR;
8501                 p++;
8502             }
8503             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8504             gameInfo.resultDetails = StrSave(p);
8505             continue;
8506         }
8507         if (boardIndex >= forwardMostMove &&
8508             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8509             backwardMostMove = blackPlaysFirst ? 1 : 0;
8510             return;
8511         }
8512         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8513                                  fromY, fromX, toY, toX, promoChar,
8514                                  parseList[boardIndex]);
8515         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8516         /* currentMoveString is set as a side-effect of yylex */
8517         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8518         strcat(moveList[boardIndex], "\n");
8519         boardIndex++;
8520         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8521         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8522           case MT_NONE:
8523           case MT_STALEMATE:
8524           default:
8525             break;
8526           case MT_CHECK:
8527             if(gameInfo.variant != VariantShogi)
8528                 strcat(parseList[boardIndex - 1], "+");
8529             break;
8530           case MT_CHECKMATE:
8531           case MT_STAINMATE:
8532             strcat(parseList[boardIndex - 1], "#");
8533             break;
8534         }
8535     }
8536 }
8537
8538
8539 /* Apply a move to the given board  */
8540 void
8541 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8542      int fromX, fromY, toX, toY;
8543      int promoChar;
8544      Board board;
8545 {
8546   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8547   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8548
8549     /* [HGM] compute & store e.p. status and castling rights for new position */
8550     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8551
8552       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8553       oldEP = (signed char)board[EP_STATUS];
8554       board[EP_STATUS] = EP_NONE;
8555
8556       if( board[toY][toX] != EmptySquare )
8557            board[EP_STATUS] = EP_CAPTURE;
8558
8559   if (fromY == DROP_RANK) {
8560         /* must be first */
8561         piece = board[toY][toX] = (ChessSquare) fromX;
8562   } else {
8563       int i;
8564
8565       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8566            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8567                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8568       } else
8569       if( board[fromY][fromX] == WhitePawn ) {
8570            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8571                board[EP_STATUS] = EP_PAWN_MOVE;
8572            if( toY-fromY==2) {
8573                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8574                         gameInfo.variant != VariantBerolina || toX < fromX)
8575                       board[EP_STATUS] = toX | berolina;
8576                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8577                         gameInfo.variant != VariantBerolina || toX > fromX)
8578                       board[EP_STATUS] = toX;
8579            }
8580       } else
8581       if( board[fromY][fromX] == BlackPawn ) {
8582            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8583                board[EP_STATUS] = EP_PAWN_MOVE;
8584            if( toY-fromY== -2) {
8585                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8586                         gameInfo.variant != VariantBerolina || toX < fromX)
8587                       board[EP_STATUS] = toX | berolina;
8588                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8589                         gameInfo.variant != VariantBerolina || toX > fromX)
8590                       board[EP_STATUS] = toX;
8591            }
8592        }
8593
8594        for(i=0; i<nrCastlingRights; i++) {
8595            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8596               board[CASTLING][i] == toX   && castlingRank[i] == toY
8597              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8598        }
8599
8600      if (fromX == toX && fromY == toY) return;
8601
8602      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8603      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8604      if(gameInfo.variant == VariantKnightmate)
8605          king += (int) WhiteUnicorn - (int) WhiteKing;
8606
8607     /* Code added by Tord: */
8608     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8609     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8610         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8611       board[fromY][fromX] = EmptySquare;
8612       board[toY][toX] = EmptySquare;
8613       if((toX > fromX) != (piece == WhiteRook)) {
8614         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8615       } else {
8616         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8617       }
8618     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8619                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8620       board[fromY][fromX] = EmptySquare;
8621       board[toY][toX] = EmptySquare;
8622       if((toX > fromX) != (piece == BlackRook)) {
8623         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8624       } else {
8625         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8626       }
8627     /* End of code added by Tord */
8628
8629     } else if (board[fromY][fromX] == king
8630         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8631         && toY == fromY && toX > fromX+1) {
8632         board[fromY][fromX] = EmptySquare;
8633         board[toY][toX] = king;
8634         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8635         board[fromY][BOARD_RGHT-1] = EmptySquare;
8636     } else if (board[fromY][fromX] == king
8637         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8638                && toY == fromY && toX < fromX-1) {
8639         board[fromY][fromX] = EmptySquare;
8640         board[toY][toX] = king;
8641         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8642         board[fromY][BOARD_LEFT] = EmptySquare;
8643     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8644                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8645                && toY >= BOARD_HEIGHT-promoRank
8646                ) {
8647         /* white pawn promotion */
8648         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8649         if (board[toY][toX] == EmptySquare) {
8650             board[toY][toX] = WhiteQueen;
8651         }
8652         if(gameInfo.variant==VariantBughouse ||
8653            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8654             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8655         board[fromY][fromX] = EmptySquare;
8656     } else if ((fromY == BOARD_HEIGHT-4)
8657                && (toX != fromX)
8658                && gameInfo.variant != VariantXiangqi
8659                && gameInfo.variant != VariantBerolina
8660                && (board[fromY][fromX] == WhitePawn)
8661                && (board[toY][toX] == EmptySquare)) {
8662         board[fromY][fromX] = EmptySquare;
8663         board[toY][toX] = WhitePawn;
8664         captured = board[toY - 1][toX];
8665         board[toY - 1][toX] = EmptySquare;
8666     } else if ((fromY == BOARD_HEIGHT-4)
8667                && (toX == fromX)
8668                && gameInfo.variant == VariantBerolina
8669                && (board[fromY][fromX] == WhitePawn)
8670                && (board[toY][toX] == EmptySquare)) {
8671         board[fromY][fromX] = EmptySquare;
8672         board[toY][toX] = WhitePawn;
8673         if(oldEP & EP_BEROLIN_A) {
8674                 captured = board[fromY][fromX-1];
8675                 board[fromY][fromX-1] = EmptySquare;
8676         }else{  captured = board[fromY][fromX+1];
8677                 board[fromY][fromX+1] = EmptySquare;
8678         }
8679     } else if (board[fromY][fromX] == king
8680         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8681                && toY == fromY && toX > fromX+1) {
8682         board[fromY][fromX] = EmptySquare;
8683         board[toY][toX] = king;
8684         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8685         board[fromY][BOARD_RGHT-1] = EmptySquare;
8686     } else if (board[fromY][fromX] == king
8687         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8688                && toY == fromY && toX < fromX-1) {
8689         board[fromY][fromX] = EmptySquare;
8690         board[toY][toX] = king;
8691         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8692         board[fromY][BOARD_LEFT] = EmptySquare;
8693     } else if (fromY == 7 && fromX == 3
8694                && board[fromY][fromX] == BlackKing
8695                && toY == 7 && toX == 5) {
8696         board[fromY][fromX] = EmptySquare;
8697         board[toY][toX] = BlackKing;
8698         board[fromY][7] = EmptySquare;
8699         board[toY][4] = BlackRook;
8700     } else if (fromY == 7 && fromX == 3
8701                && board[fromY][fromX] == BlackKing
8702                && toY == 7 && toX == 1) {
8703         board[fromY][fromX] = EmptySquare;
8704         board[toY][toX] = BlackKing;
8705         board[fromY][0] = EmptySquare;
8706         board[toY][2] = BlackRook;
8707     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8708                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8709                && toY < promoRank
8710                ) {
8711         /* black pawn promotion */
8712         board[toY][toX] = CharToPiece(ToLower(promoChar));
8713         if (board[toY][toX] == EmptySquare) {
8714             board[toY][toX] = BlackQueen;
8715         }
8716         if(gameInfo.variant==VariantBughouse ||
8717            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8718             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8719         board[fromY][fromX] = EmptySquare;
8720     } else if ((fromY == 3)
8721                && (toX != fromX)
8722                && gameInfo.variant != VariantXiangqi
8723                && gameInfo.variant != VariantBerolina
8724                && (board[fromY][fromX] == BlackPawn)
8725                && (board[toY][toX] == EmptySquare)) {
8726         board[fromY][fromX] = EmptySquare;
8727         board[toY][toX] = BlackPawn;
8728         captured = board[toY + 1][toX];
8729         board[toY + 1][toX] = EmptySquare;
8730     } else if ((fromY == 3)
8731                && (toX == fromX)
8732                && gameInfo.variant == VariantBerolina
8733                && (board[fromY][fromX] == BlackPawn)
8734                && (board[toY][toX] == EmptySquare)) {
8735         board[fromY][fromX] = EmptySquare;
8736         board[toY][toX] = BlackPawn;
8737         if(oldEP & EP_BEROLIN_A) {
8738                 captured = board[fromY][fromX-1];
8739                 board[fromY][fromX-1] = EmptySquare;
8740         }else{  captured = board[fromY][fromX+1];
8741                 board[fromY][fromX+1] = EmptySquare;
8742         }
8743     } else {
8744         board[toY][toX] = board[fromY][fromX];
8745         board[fromY][fromX] = EmptySquare;
8746     }
8747   }
8748
8749     if (gameInfo.holdingsWidth != 0) {
8750
8751       /* !!A lot more code needs to be written to support holdings  */
8752       /* [HGM] OK, so I have written it. Holdings are stored in the */
8753       /* penultimate board files, so they are automaticlly stored   */
8754       /* in the game history.                                       */
8755       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8756                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8757         /* Delete from holdings, by decreasing count */
8758         /* and erasing image if necessary            */
8759         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8760         if(p < (int) BlackPawn) { /* white drop */
8761              p -= (int)WhitePawn;
8762                  p = PieceToNumber((ChessSquare)p);
8763              if(p >= gameInfo.holdingsSize) p = 0;
8764              if(--board[p][BOARD_WIDTH-2] <= 0)
8765                   board[p][BOARD_WIDTH-1] = EmptySquare;
8766              if((int)board[p][BOARD_WIDTH-2] < 0)
8767                         board[p][BOARD_WIDTH-2] = 0;
8768         } else {                  /* black drop */
8769              p -= (int)BlackPawn;
8770                  p = PieceToNumber((ChessSquare)p);
8771              if(p >= gameInfo.holdingsSize) p = 0;
8772              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8773                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8774              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8775                         board[BOARD_HEIGHT-1-p][1] = 0;
8776         }
8777       }
8778       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8779           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8780         /* [HGM] holdings: Add to holdings, if holdings exist */
8781         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8782                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8783                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8784         }
8785         p = (int) captured;
8786         if (p >= (int) BlackPawn) {
8787           p -= (int)BlackPawn;
8788           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8789                   /* in Shogi restore piece to its original  first */
8790                   captured = (ChessSquare) (DEMOTED captured);
8791                   p = DEMOTED p;
8792           }
8793           p = PieceToNumber((ChessSquare)p);
8794           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8795           board[p][BOARD_WIDTH-2]++;
8796           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8797         } else {
8798           p -= (int)WhitePawn;
8799           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8800                   captured = (ChessSquare) (DEMOTED captured);
8801                   p = DEMOTED p;
8802           }
8803           p = PieceToNumber((ChessSquare)p);
8804           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8805           board[BOARD_HEIGHT-1-p][1]++;
8806           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8807         }
8808       }
8809     } else if (gameInfo.variant == VariantAtomic) {
8810       if (captured != EmptySquare) {
8811         int y, x;
8812         for (y = toY-1; y <= toY+1; y++) {
8813           for (x = toX-1; x <= toX+1; x++) {
8814             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8815                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8816               board[y][x] = EmptySquare;
8817             }
8818           }
8819         }
8820         board[toY][toX] = EmptySquare;
8821       }
8822     }
8823     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8824         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8825     } else
8826     if(promoChar == '+') {
8827         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8828         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8829     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8830         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8831     }
8832     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8833                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8834         // [HGM] superchess: take promotion piece out of holdings
8835         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8836         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8837             if(!--board[k][BOARD_WIDTH-2])
8838                 board[k][BOARD_WIDTH-1] = EmptySquare;
8839         } else {
8840             if(!--board[BOARD_HEIGHT-1-k][1])
8841                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8842         }
8843     }
8844
8845 }
8846
8847 /* Updates forwardMostMove */
8848 void
8849 MakeMove(fromX, fromY, toX, toY, promoChar)
8850      int fromX, fromY, toX, toY;
8851      int promoChar;
8852 {
8853 //    forwardMostMove++; // [HGM] bare: moved downstream
8854
8855     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8856         int timeLeft; static int lastLoadFlag=0; int king, piece;
8857         piece = boards[forwardMostMove][fromY][fromX];
8858         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8859         if(gameInfo.variant == VariantKnightmate)
8860             king += (int) WhiteUnicorn - (int) WhiteKing;
8861         if(forwardMostMove == 0) {
8862             if(blackPlaysFirst)
8863                 fprintf(serverMoves, "%s;", second.tidy);
8864             fprintf(serverMoves, "%s;", first.tidy);
8865             if(!blackPlaysFirst)
8866                 fprintf(serverMoves, "%s;", second.tidy);
8867         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8868         lastLoadFlag = loadFlag;
8869         // print base move
8870         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8871         // print castling suffix
8872         if( toY == fromY && piece == king ) {
8873             if(toX-fromX > 1)
8874                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8875             if(fromX-toX >1)
8876                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8877         }
8878         // e.p. suffix
8879         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8880              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8881              boards[forwardMostMove][toY][toX] == EmptySquare
8882              && fromX != toX && fromY != toY)
8883                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8884         // promotion suffix
8885         if(promoChar != NULLCHAR)
8886                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8887         if(!loadFlag) {
8888             fprintf(serverMoves, "/%d/%d",
8889                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8890             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8891             else                      timeLeft = blackTimeRemaining/1000;
8892             fprintf(serverMoves, "/%d", timeLeft);
8893         }
8894         fflush(serverMoves);
8895     }
8896
8897     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8899                         0, 1);
8900       return;
8901     }
8902     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8903     if (commentList[forwardMostMove+1] != NULL) {
8904         free(commentList[forwardMostMove+1]);
8905         commentList[forwardMostMove+1] = NULL;
8906     }
8907     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8908     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8909     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8910     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8911     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8912     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8913     gameInfo.result = GameUnfinished;
8914     if (gameInfo.resultDetails != NULL) {
8915         free(gameInfo.resultDetails);
8916         gameInfo.resultDetails = NULL;
8917     }
8918     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8919                               moveList[forwardMostMove - 1]);
8920     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8921                              PosFlags(forwardMostMove - 1),
8922                              fromY, fromX, toY, toX, promoChar,
8923                              parseList[forwardMostMove - 1]);
8924     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8925       case MT_NONE:
8926       case MT_STALEMATE:
8927       default:
8928         break;
8929       case MT_CHECK:
8930         if(gameInfo.variant != VariantShogi)
8931             strcat(parseList[forwardMostMove - 1], "+");
8932         break;
8933       case MT_CHECKMATE:
8934       case MT_STAINMATE:
8935         strcat(parseList[forwardMostMove - 1], "#");
8936         break;
8937     }
8938     if (appData.debugMode) {
8939         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8940     }
8941
8942 }
8943
8944 /* Updates currentMove if not pausing */
8945 void
8946 ShowMove(fromX, fromY, toX, toY)
8947 {
8948     int instant = (gameMode == PlayFromGameFile) ?
8949         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8950     if(appData.noGUI) return;
8951     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8952         if (!instant) {
8953             if (forwardMostMove == currentMove + 1) {
8954                 AnimateMove(boards[forwardMostMove - 1],
8955                             fromX, fromY, toX, toY);
8956             }
8957             if (appData.highlightLastMove) {
8958                 SetHighlights(fromX, fromY, toX, toY);
8959             }
8960         }
8961         currentMove = forwardMostMove;
8962     }
8963
8964     if (instant) return;
8965
8966     DisplayMove(currentMove - 1);
8967     DrawPosition(FALSE, boards[currentMove]);
8968     DisplayBothClocks();
8969     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8970 }
8971
8972 void SendEgtPath(ChessProgramState *cps)
8973 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8974         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8975
8976         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8977
8978         while(*p) {
8979             char c, *q = name+1, *r, *s;
8980
8981             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8982             while(*p && *p != ',') *q++ = *p++;
8983             *q++ = ':'; *q = 0;
8984             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8985                 strcmp(name, ",nalimov:") == 0 ) {
8986                 // take nalimov path from the menu-changeable option first, if it is defined
8987               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8988                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8989             } else
8990             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8991                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8992                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8993                 s = r = StrStr(s, ":") + 1; // beginning of path info
8994                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8995                 c = *r; *r = 0;             // temporarily null-terminate path info
8996                     *--q = 0;               // strip of trailig ':' from name
8997                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8998                 *r = c;
8999                 SendToProgram(buf,cps);     // send egtbpath command for this format
9000             }
9001             if(*p == ',') p++; // read away comma to position for next format name
9002         }
9003 }
9004
9005 void
9006 InitChessProgram(cps, setup)
9007      ChessProgramState *cps;
9008      int setup; /* [HGM] needed to setup FRC opening position */
9009 {
9010     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9011     if (appData.noChessProgram) return;
9012     hintRequested = FALSE;
9013     bookRequested = FALSE;
9014
9015     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9016     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9017     if(cps->memSize) { /* [HGM] memory */
9018       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9019         SendToProgram(buf, cps);
9020     }
9021     SendEgtPath(cps); /* [HGM] EGT */
9022     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9023       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9024         SendToProgram(buf, cps);
9025     }
9026
9027     SendToProgram(cps->initString, cps);
9028     if (gameInfo.variant != VariantNormal &&
9029         gameInfo.variant != VariantLoadable
9030         /* [HGM] also send variant if board size non-standard */
9031         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9032                                             ) {
9033       char *v = VariantName(gameInfo.variant);
9034       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9035         /* [HGM] in protocol 1 we have to assume all variants valid */
9036         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9037         DisplayFatalError(buf, 0, 1);
9038         return;
9039       }
9040
9041       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9042       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9043       if( gameInfo.variant == VariantXiangqi )
9044            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9045       if( gameInfo.variant == VariantShogi )
9046            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9047       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9048            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9049       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9050           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9051            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9052       if( gameInfo.variant == VariantCourier )
9053            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9054       if( gameInfo.variant == VariantSuper )
9055            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9056       if( gameInfo.variant == VariantGreat )
9057            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9058       if( gameInfo.variant == VariantSChess )
9059            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9060
9061       if(overruled) {
9062         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9063                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9064            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9065            if(StrStr(cps->variants, b) == NULL) {
9066                // specific sized variant not known, check if general sizing allowed
9067                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9068                    if(StrStr(cps->variants, "boardsize") == NULL) {
9069                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9070                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9071                        DisplayFatalError(buf, 0, 1);
9072                        return;
9073                    }
9074                    /* [HGM] here we really should compare with the maximum supported board size */
9075                }
9076            }
9077       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9078       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9079       SendToProgram(buf, cps);
9080     }
9081     currentlyInitializedVariant = gameInfo.variant;
9082
9083     /* [HGM] send opening position in FRC to first engine */
9084     if(setup) {
9085           SendToProgram("force\n", cps);
9086           SendBoard(cps, 0);
9087           /* engine is now in force mode! Set flag to wake it up after first move. */
9088           setboardSpoiledMachineBlack = 1;
9089     }
9090
9091     if (cps->sendICS) {
9092       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9093       SendToProgram(buf, cps);
9094     }
9095     cps->maybeThinking = FALSE;
9096     cps->offeredDraw = 0;
9097     if (!appData.icsActive) {
9098         SendTimeControl(cps, movesPerSession, timeControl,
9099                         timeIncrement, appData.searchDepth,
9100                         searchTime);
9101     }
9102     if (appData.showThinking
9103         // [HGM] thinking: four options require thinking output to be sent
9104         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9105                                 ) {
9106         SendToProgram("post\n", cps);
9107     }
9108     SendToProgram("hard\n", cps);
9109     if (!appData.ponderNextMove) {
9110         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9111            it without being sure what state we are in first.  "hard"
9112            is not a toggle, so that one is OK.
9113          */
9114         SendToProgram("easy\n", cps);
9115     }
9116     if (cps->usePing) {
9117       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9118       SendToProgram(buf, cps);
9119     }
9120     cps->initDone = TRUE;
9121 }
9122
9123
9124 void
9125 StartChessProgram(cps)
9126      ChessProgramState *cps;
9127 {
9128     char buf[MSG_SIZ];
9129     int err;
9130
9131     if (appData.noChessProgram) return;
9132     cps->initDone = FALSE;
9133
9134     if (strcmp(cps->host, "localhost") == 0) {
9135         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9136     } else if (*appData.remoteShell == NULLCHAR) {
9137         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9138     } else {
9139         if (*appData.remoteUser == NULLCHAR) {
9140           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9141                     cps->program);
9142         } else {
9143           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9144                     cps->host, appData.remoteUser, cps->program);
9145         }
9146         err = StartChildProcess(buf, "", &cps->pr);
9147     }
9148
9149     if (err != 0) {
9150       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9151         DisplayFatalError(buf, err, 1);
9152         cps->pr = NoProc;
9153         cps->isr = NULL;
9154         return;
9155     }
9156
9157     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9158     if (cps->protocolVersion > 1) {
9159       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9160       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9161       cps->comboCnt = 0;  //                and values of combo boxes
9162       SendToProgram(buf, cps);
9163     } else {
9164       SendToProgram("xboard\n", cps);
9165     }
9166 }
9167
9168
9169 void
9170 TwoMachinesEventIfReady P((void))
9171 {
9172   if (first.lastPing != first.lastPong) {
9173     DisplayMessage("", _("Waiting for first chess program"));
9174     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9175     return;
9176   }
9177   if (second.lastPing != second.lastPong) {
9178     DisplayMessage("", _("Waiting for second chess program"));
9179     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9180     return;
9181   }
9182   ThawUI();
9183   TwoMachinesEvent();
9184 }
9185
9186 void
9187 NextMatchGame P((void))
9188 {
9189     int index; /* [HGM] autoinc: step load index during match */
9190     Reset(FALSE, TRUE);
9191     if (*appData.loadGameFile != NULLCHAR) {
9192         index = appData.loadGameIndex;
9193         if(index < 0) { // [HGM] autoinc
9194             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9195             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9196         }
9197         LoadGameFromFile(appData.loadGameFile,
9198                          index,
9199                          appData.loadGameFile, FALSE);
9200     } else if (*appData.loadPositionFile != NULLCHAR) {
9201         index = appData.loadPositionIndex;
9202         if(index < 0) { // [HGM] autoinc
9203             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9204             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9205         }
9206         LoadPositionFromFile(appData.loadPositionFile,
9207                              index,
9208                              appData.loadPositionFile);
9209     }
9210     TwoMachinesEventIfReady();
9211 }
9212
9213 void UserAdjudicationEvent( int result )
9214 {
9215     ChessMove gameResult = GameIsDrawn;
9216
9217     if( result > 0 ) {
9218         gameResult = WhiteWins;
9219     }
9220     else if( result < 0 ) {
9221         gameResult = BlackWins;
9222     }
9223
9224     if( gameMode == TwoMachinesPlay ) {
9225         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9226     }
9227 }
9228
9229
9230 // [HGM] save: calculate checksum of game to make games easily identifiable
9231 int StringCheckSum(char *s)
9232 {
9233         int i = 0;
9234         if(s==NULL) return 0;
9235         while(*s) i = i*259 + *s++;
9236         return i;
9237 }
9238
9239 int GameCheckSum()
9240 {
9241         int i, sum=0;
9242         for(i=backwardMostMove; i<forwardMostMove; i++) {
9243                 sum += pvInfoList[i].depth;
9244                 sum += StringCheckSum(parseList[i]);
9245                 sum += StringCheckSum(commentList[i]);
9246                 sum *= 261;
9247         }
9248         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9249         return sum + StringCheckSum(commentList[i]);
9250 } // end of save patch
9251
9252 void
9253 GameEnds(result, resultDetails, whosays)
9254      ChessMove result;
9255      char *resultDetails;
9256      int whosays;
9257 {
9258     GameMode nextGameMode;
9259     int isIcsGame;
9260     char buf[MSG_SIZ], popupRequested = 0;
9261
9262     if(endingGame) return; /* [HGM] crash: forbid recursion */
9263     endingGame = 1;
9264     if(twoBoards) { // [HGM] dual: switch back to one board
9265         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9266         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9267     }
9268     if (appData.debugMode) {
9269       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9270               result, resultDetails ? resultDetails : "(null)", whosays);
9271     }
9272
9273     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9274
9275     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9276         /* If we are playing on ICS, the server decides when the
9277            game is over, but the engine can offer to draw, claim
9278            a draw, or resign.
9279          */
9280 #if ZIPPY
9281         if (appData.zippyPlay && first.initDone) {
9282             if (result == GameIsDrawn) {
9283                 /* In case draw still needs to be claimed */
9284                 SendToICS(ics_prefix);
9285                 SendToICS("draw\n");
9286             } else if (StrCaseStr(resultDetails, "resign")) {
9287                 SendToICS(ics_prefix);
9288                 SendToICS("resign\n");
9289             }
9290         }
9291 #endif
9292         endingGame = 0; /* [HGM] crash */
9293         return;
9294     }
9295
9296     /* If we're loading the game from a file, stop */
9297     if (whosays == GE_FILE) {
9298       (void) StopLoadGameTimer();
9299       gameFileFP = NULL;
9300     }
9301
9302     /* Cancel draw offers */
9303     first.offeredDraw = second.offeredDraw = 0;
9304
9305     /* If this is an ICS game, only ICS can really say it's done;
9306        if not, anyone can. */
9307     isIcsGame = (gameMode == IcsPlayingWhite ||
9308                  gameMode == IcsPlayingBlack ||
9309                  gameMode == IcsObserving    ||
9310                  gameMode == IcsExamining);
9311
9312     if (!isIcsGame || whosays == GE_ICS) {
9313         /* OK -- not an ICS game, or ICS said it was done */
9314         StopClocks();
9315         if (!isIcsGame && !appData.noChessProgram)
9316           SetUserThinkingEnables();
9317
9318         /* [HGM] if a machine claims the game end we verify this claim */
9319         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9320             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9321                 char claimer;
9322                 ChessMove trueResult = (ChessMove) -1;
9323
9324                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9325                                             first.twoMachinesColor[0] :
9326                                             second.twoMachinesColor[0] ;
9327
9328                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9329                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9330                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9331                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9332                 } else
9333                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9334                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9335                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9336                 } else
9337                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9338                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9339                 }
9340
9341                 // now verify win claims, but not in drop games, as we don't understand those yet
9342                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9343                                                  || gameInfo.variant == VariantGreat) &&
9344                     (result == WhiteWins && claimer == 'w' ||
9345                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9346                       if (appData.debugMode) {
9347                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9348                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9349                       }
9350                       if(result != trueResult) {
9351                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9352                               result = claimer == 'w' ? BlackWins : WhiteWins;
9353                               resultDetails = buf;
9354                       }
9355                 } else
9356                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9357                     && (forwardMostMove <= backwardMostMove ||
9358                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9359                         (claimer=='b')==(forwardMostMove&1))
9360                                                                                   ) {
9361                       /* [HGM] verify: draws that were not flagged are false claims */
9362                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9363                       result = claimer == 'w' ? BlackWins : WhiteWins;
9364                       resultDetails = buf;
9365                 }
9366                 /* (Claiming a loss is accepted no questions asked!) */
9367             }
9368             /* [HGM] bare: don't allow bare King to win */
9369             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9370                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9371                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9372                && result != GameIsDrawn)
9373             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9374                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9375                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9376                         if(p >= 0 && p <= (int)WhiteKing) k++;
9377                 }
9378                 if (appData.debugMode) {
9379                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9380                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9381                 }
9382                 if(k <= 1) {
9383                         result = GameIsDrawn;
9384                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9385                         resultDetails = buf;
9386                 }
9387             }
9388         }
9389
9390
9391         if(serverMoves != NULL && !loadFlag) { char c = '=';
9392             if(result==WhiteWins) c = '+';
9393             if(result==BlackWins) c = '-';
9394             if(resultDetails != NULL)
9395                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9396         }
9397         if (resultDetails != NULL) {
9398             gameInfo.result = result;
9399             gameInfo.resultDetails = StrSave(resultDetails);
9400
9401             /* display last move only if game was not loaded from file */
9402             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9403                 DisplayMove(currentMove - 1);
9404
9405             if (forwardMostMove != 0) {
9406                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9407                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9408                                                                 ) {
9409                     if (*appData.saveGameFile != NULLCHAR) {
9410                         SaveGameToFile(appData.saveGameFile, TRUE);
9411                     } else if (appData.autoSaveGames) {
9412                         AutoSaveGame();
9413                     }
9414                     if (*appData.savePositionFile != NULLCHAR) {
9415                         SavePositionToFile(appData.savePositionFile);
9416                     }
9417                 }
9418             }
9419
9420             /* Tell program how game ended in case it is learning */
9421             /* [HGM] Moved this to after saving the PGN, just in case */
9422             /* engine died and we got here through time loss. In that */
9423             /* case we will get a fatal error writing the pipe, which */
9424             /* would otherwise lose us the PGN.                       */
9425             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9426             /* output during GameEnds should never be fatal anymore   */
9427             if (gameMode == MachinePlaysWhite ||
9428                 gameMode == MachinePlaysBlack ||
9429                 gameMode == TwoMachinesPlay ||
9430                 gameMode == IcsPlayingWhite ||
9431                 gameMode == IcsPlayingBlack ||
9432                 gameMode == BeginningOfGame) {
9433                 char buf[MSG_SIZ];
9434                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9435                         resultDetails);
9436                 if (first.pr != NoProc) {
9437                     SendToProgram(buf, &first);
9438                 }
9439                 if (second.pr != NoProc &&
9440                     gameMode == TwoMachinesPlay) {
9441                     SendToProgram(buf, &second);
9442                 }
9443             }
9444         }
9445
9446         if (appData.icsActive) {
9447             if (appData.quietPlay &&
9448                 (gameMode == IcsPlayingWhite ||
9449                  gameMode == IcsPlayingBlack)) {
9450                 SendToICS(ics_prefix);
9451                 SendToICS("set shout 1\n");
9452             }
9453             nextGameMode = IcsIdle;
9454             ics_user_moved = FALSE;
9455             /* clean up premove.  It's ugly when the game has ended and the
9456              * premove highlights are still on the board.
9457              */
9458             if (gotPremove) {
9459               gotPremove = FALSE;
9460               ClearPremoveHighlights();
9461               DrawPosition(FALSE, boards[currentMove]);
9462             }
9463             if (whosays == GE_ICS) {
9464                 switch (result) {
9465                 case WhiteWins:
9466                     if (gameMode == IcsPlayingWhite)
9467                         PlayIcsWinSound();
9468                     else if(gameMode == IcsPlayingBlack)
9469                         PlayIcsLossSound();
9470                     break;
9471                 case BlackWins:
9472                     if (gameMode == IcsPlayingBlack)
9473                         PlayIcsWinSound();
9474                     else if(gameMode == IcsPlayingWhite)
9475                         PlayIcsLossSound();
9476                     break;
9477                 case GameIsDrawn:
9478                     PlayIcsDrawSound();
9479                     break;
9480                 default:
9481                     PlayIcsUnfinishedSound();
9482                 }
9483             }
9484         } else if (gameMode == EditGame ||
9485                    gameMode == PlayFromGameFile ||
9486                    gameMode == AnalyzeMode ||
9487                    gameMode == AnalyzeFile) {
9488             nextGameMode = gameMode;
9489         } else {
9490             nextGameMode = EndOfGame;
9491         }
9492         pausing = FALSE;
9493         ModeHighlight();
9494     } else {
9495         nextGameMode = gameMode;
9496     }
9497
9498     if (appData.noChessProgram) {
9499         gameMode = nextGameMode;
9500         ModeHighlight();
9501         endingGame = 0; /* [HGM] crash */
9502         return;
9503     }
9504
9505     if (first.reuse) {
9506         /* Put first chess program into idle state */
9507         if (first.pr != NoProc &&
9508             (gameMode == MachinePlaysWhite ||
9509              gameMode == MachinePlaysBlack ||
9510              gameMode == TwoMachinesPlay ||
9511              gameMode == IcsPlayingWhite ||
9512              gameMode == IcsPlayingBlack ||
9513              gameMode == BeginningOfGame)) {
9514             SendToProgram("force\n", &first);
9515             if (first.usePing) {
9516               char buf[MSG_SIZ];
9517               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9518               SendToProgram(buf, &first);
9519             }
9520         }
9521     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9522         /* Kill off first chess program */
9523         if (first.isr != NULL)
9524           RemoveInputSource(first.isr);
9525         first.isr = NULL;
9526
9527         if (first.pr != NoProc) {
9528             ExitAnalyzeMode();
9529             DoSleep( appData.delayBeforeQuit );
9530             SendToProgram("quit\n", &first);
9531             DoSleep( appData.delayAfterQuit );
9532             DestroyChildProcess(first.pr, first.useSigterm);
9533         }
9534         first.pr = NoProc;
9535     }
9536     if (second.reuse) {
9537         /* Put second chess program into idle state */
9538         if (second.pr != NoProc &&
9539             gameMode == TwoMachinesPlay) {
9540             SendToProgram("force\n", &second);
9541             if (second.usePing) {
9542               char buf[MSG_SIZ];
9543               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9544               SendToProgram(buf, &second);
9545             }
9546         }
9547     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9548         /* Kill off second chess program */
9549         if (second.isr != NULL)
9550           RemoveInputSource(second.isr);
9551         second.isr = NULL;
9552
9553         if (second.pr != NoProc) {
9554             DoSleep( appData.delayBeforeQuit );
9555             SendToProgram("quit\n", &second);
9556             DoSleep( appData.delayAfterQuit );
9557             DestroyChildProcess(second.pr, second.useSigterm);
9558         }
9559         second.pr = NoProc;
9560     }
9561
9562     if (matchMode && gameMode == TwoMachinesPlay) {
9563         switch (result) {
9564         case WhiteWins:
9565           if (first.twoMachinesColor[0] == 'w') {
9566             first.matchWins++;
9567           } else {
9568             second.matchWins++;
9569           }
9570           break;
9571         case BlackWins:
9572           if (first.twoMachinesColor[0] == 'b') {
9573             first.matchWins++;
9574           } else {
9575             second.matchWins++;
9576           }
9577           break;
9578         default:
9579           break;
9580         }
9581         if (matchGame < appData.matchGames) {
9582             char *tmp;
9583             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9584                 tmp = first.twoMachinesColor;
9585                 first.twoMachinesColor = second.twoMachinesColor;
9586                 second.twoMachinesColor = tmp;
9587             }
9588             gameMode = nextGameMode;
9589             matchGame++;
9590             if(appData.matchPause>10000 || appData.matchPause<10)
9591                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9592             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9593             endingGame = 0; /* [HGM] crash */
9594             return;
9595         } else {
9596             gameMode = nextGameMode;
9597             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9598                      first.tidy, second.tidy,
9599                      first.matchWins, second.matchWins,
9600                      appData.matchGames - (first.matchWins + second.matchWins));
9601             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9602             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9603                 first.twoMachinesColor = "black\n";
9604                 second.twoMachinesColor = "white\n";
9605             } else {
9606                 first.twoMachinesColor = "white\n";
9607                 second.twoMachinesColor = "black\n";
9608             }
9609         }
9610     }
9611     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9612         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9613       ExitAnalyzeMode();
9614     gameMode = nextGameMode;
9615     ModeHighlight();
9616     endingGame = 0;  /* [HGM] crash */
9617     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9618       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9619         matchMode = FALSE; appData.matchGames = matchGame = 0;
9620         DisplayNote(buf);
9621       }
9622     }
9623 }
9624
9625 /* Assumes program was just initialized (initString sent).
9626    Leaves program in force mode. */
9627 void
9628 FeedMovesToProgram(cps, upto)
9629      ChessProgramState *cps;
9630      int upto;
9631 {
9632     int i;
9633
9634     if (appData.debugMode)
9635       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9636               startedFromSetupPosition ? "position and " : "",
9637               backwardMostMove, upto, cps->which);
9638     if(currentlyInitializedVariant != gameInfo.variant) {
9639       char buf[MSG_SIZ];
9640         // [HGM] variantswitch: make engine aware of new variant
9641         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9642                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9643         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9644         SendToProgram(buf, cps);
9645         currentlyInitializedVariant = gameInfo.variant;
9646     }
9647     SendToProgram("force\n", cps);
9648     if (startedFromSetupPosition) {
9649         SendBoard(cps, backwardMostMove);
9650     if (appData.debugMode) {
9651         fprintf(debugFP, "feedMoves\n");
9652     }
9653     }
9654     for (i = backwardMostMove; i < upto; i++) {
9655         SendMoveToProgram(i, cps);
9656     }
9657 }
9658
9659
9660 void
9661 ResurrectChessProgram()
9662 {
9663      /* The chess program may have exited.
9664         If so, restart it and feed it all the moves made so far. */
9665
9666     if (appData.noChessProgram || first.pr != NoProc) return;
9667
9668     StartChessProgram(&first);
9669     InitChessProgram(&first, FALSE);
9670     FeedMovesToProgram(&first, currentMove);
9671
9672     if (!first.sendTime) {
9673         /* can't tell gnuchess what its clock should read,
9674            so we bow to its notion. */
9675         ResetClocks();
9676         timeRemaining[0][currentMove] = whiteTimeRemaining;
9677         timeRemaining[1][currentMove] = blackTimeRemaining;
9678     }
9679
9680     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9681                 appData.icsEngineAnalyze) && first.analysisSupport) {
9682       SendToProgram("analyze\n", &first);
9683       first.analyzing = TRUE;
9684     }
9685 }
9686
9687 /*
9688  * Button procedures
9689  */
9690 void
9691 Reset(redraw, init)
9692      int redraw, init;
9693 {
9694     int i;
9695
9696     if (appData.debugMode) {
9697         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9698                 redraw, init, gameMode);
9699     }
9700     CleanupTail(); // [HGM] vari: delete any stored variations
9701     pausing = pauseExamInvalid = FALSE;
9702     startedFromSetupPosition = blackPlaysFirst = FALSE;
9703     firstMove = TRUE;
9704     whiteFlag = blackFlag = FALSE;
9705     userOfferedDraw = FALSE;
9706     hintRequested = bookRequested = FALSE;
9707     first.maybeThinking = FALSE;
9708     second.maybeThinking = FALSE;
9709     first.bookSuspend = FALSE; // [HGM] book
9710     second.bookSuspend = FALSE;
9711     thinkOutput[0] = NULLCHAR;
9712     lastHint[0] = NULLCHAR;
9713     ClearGameInfo(&gameInfo);
9714     gameInfo.variant = StringToVariant(appData.variant);
9715     ics_user_moved = ics_clock_paused = FALSE;
9716     ics_getting_history = H_FALSE;
9717     ics_gamenum = -1;
9718     white_holding[0] = black_holding[0] = NULLCHAR;
9719     ClearProgramStats();
9720     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9721
9722     ResetFrontEnd();
9723     ClearHighlights();
9724     flipView = appData.flipView;
9725     ClearPremoveHighlights();
9726     gotPremove = FALSE;
9727     alarmSounded = FALSE;
9728
9729     GameEnds(EndOfFile, NULL, GE_PLAYER);
9730     if(appData.serverMovesName != NULL) {
9731         /* [HGM] prepare to make moves file for broadcasting */
9732         clock_t t = clock();
9733         if(serverMoves != NULL) fclose(serverMoves);
9734         serverMoves = fopen(appData.serverMovesName, "r");
9735         if(serverMoves != NULL) {
9736             fclose(serverMoves);
9737             /* delay 15 sec before overwriting, so all clients can see end */
9738             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9739         }
9740         serverMoves = fopen(appData.serverMovesName, "w");
9741     }
9742
9743     ExitAnalyzeMode();
9744     gameMode = BeginningOfGame;
9745     ModeHighlight();
9746     if(appData.icsActive) gameInfo.variant = VariantNormal;
9747     currentMove = forwardMostMove = backwardMostMove = 0;
9748     InitPosition(redraw);
9749     for (i = 0; i < MAX_MOVES; i++) {
9750         if (commentList[i] != NULL) {
9751             free(commentList[i]);
9752             commentList[i] = NULL;
9753         }
9754     }
9755     ResetClocks();
9756     timeRemaining[0][0] = whiteTimeRemaining;
9757     timeRemaining[1][0] = blackTimeRemaining;
9758     if (first.pr == NULL) {
9759         StartChessProgram(&first);
9760     }
9761     if (init) {
9762             InitChessProgram(&first, startedFromSetupPosition);
9763     }
9764     DisplayTitle("");
9765     DisplayMessage("", "");
9766     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9767     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9768 }
9769
9770 void
9771 AutoPlayGameLoop()
9772 {
9773     for (;;) {
9774         if (!AutoPlayOneMove())
9775           return;
9776         if (matchMode || appData.timeDelay == 0)
9777           continue;
9778         if (appData.timeDelay < 0)
9779           return;
9780         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9781         break;
9782     }
9783 }
9784
9785
9786 int
9787 AutoPlayOneMove()
9788 {
9789     int fromX, fromY, toX, toY;
9790
9791     if (appData.debugMode) {
9792       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9793     }
9794
9795     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9796       return FALSE;
9797
9798     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9799       pvInfoList[currentMove].depth = programStats.depth;
9800       pvInfoList[currentMove].score = programStats.score;
9801       pvInfoList[currentMove].time  = 0;
9802       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9803     }
9804
9805     if (currentMove >= forwardMostMove) {
9806       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9807       gameMode = EditGame;
9808       ModeHighlight();
9809
9810       /* [AS] Clear current move marker at the end of a game */
9811       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9812
9813       return FALSE;
9814     }
9815
9816     toX = moveList[currentMove][2] - AAA;
9817     toY = moveList[currentMove][3] - ONE;
9818
9819     if (moveList[currentMove][1] == '@') {
9820         if (appData.highlightLastMove) {
9821             SetHighlights(-1, -1, toX, toY);
9822         }
9823     } else {
9824         fromX = moveList[currentMove][0] - AAA;
9825         fromY = moveList[currentMove][1] - ONE;
9826
9827         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9828
9829         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9830
9831         if (appData.highlightLastMove) {
9832             SetHighlights(fromX, fromY, toX, toY);
9833         }
9834     }
9835     DisplayMove(currentMove);
9836     SendMoveToProgram(currentMove++, &first);
9837     DisplayBothClocks();
9838     DrawPosition(FALSE, boards[currentMove]);
9839     // [HGM] PV info: always display, routine tests if empty
9840     DisplayComment(currentMove - 1, commentList[currentMove]);
9841     return TRUE;
9842 }
9843
9844
9845 int
9846 LoadGameOneMove(readAhead)
9847      ChessMove readAhead;
9848 {
9849     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9850     char promoChar = NULLCHAR;
9851     ChessMove moveType;
9852     char move[MSG_SIZ];
9853     char *p, *q;
9854
9855     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9856         gameMode != AnalyzeMode && gameMode != Training) {
9857         gameFileFP = NULL;
9858         return FALSE;
9859     }
9860
9861     yyboardindex = forwardMostMove;
9862     if (readAhead != EndOfFile) {
9863       moveType = readAhead;
9864     } else {
9865       if (gameFileFP == NULL)
9866           return FALSE;
9867       moveType = (ChessMove) Myylex();
9868     }
9869
9870     done = FALSE;
9871     switch (moveType) {
9872       case Comment:
9873         if (appData.debugMode)
9874           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9875         p = yy_text;
9876
9877         /* append the comment but don't display it */
9878         AppendComment(currentMove, p, FALSE);
9879         return TRUE;
9880
9881       case WhiteCapturesEnPassant:
9882       case BlackCapturesEnPassant:
9883       case WhitePromotion:
9884       case BlackPromotion:
9885       case WhiteNonPromotion:
9886       case BlackNonPromotion:
9887       case NormalMove:
9888       case WhiteKingSideCastle:
9889       case WhiteQueenSideCastle:
9890       case BlackKingSideCastle:
9891       case BlackQueenSideCastle:
9892       case WhiteKingSideCastleWild:
9893       case WhiteQueenSideCastleWild:
9894       case BlackKingSideCastleWild:
9895       case BlackQueenSideCastleWild:
9896       /* PUSH Fabien */
9897       case WhiteHSideCastleFR:
9898       case WhiteASideCastleFR:
9899       case BlackHSideCastleFR:
9900       case BlackASideCastleFR:
9901       /* POP Fabien */
9902         if (appData.debugMode)
9903           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9904         fromX = currentMoveString[0] - AAA;
9905         fromY = currentMoveString[1] - ONE;
9906         toX = currentMoveString[2] - AAA;
9907         toY = currentMoveString[3] - ONE;
9908         promoChar = currentMoveString[4];
9909         break;
9910
9911       case WhiteDrop:
9912       case BlackDrop:
9913         if (appData.debugMode)
9914           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9915         fromX = moveType == WhiteDrop ?
9916           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9917         (int) CharToPiece(ToLower(currentMoveString[0]));
9918         fromY = DROP_RANK;
9919         toX = currentMoveString[2] - AAA;
9920         toY = currentMoveString[3] - ONE;
9921         break;
9922
9923       case WhiteWins:
9924       case BlackWins:
9925       case GameIsDrawn:
9926       case GameUnfinished:
9927         if (appData.debugMode)
9928           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9929         p = strchr(yy_text, '{');
9930         if (p == NULL) p = strchr(yy_text, '(');
9931         if (p == NULL) {
9932             p = yy_text;
9933             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9934         } else {
9935             q = strchr(p, *p == '{' ? '}' : ')');
9936             if (q != NULL) *q = NULLCHAR;
9937             p++;
9938         }
9939         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9940         GameEnds(moveType, p, GE_FILE);
9941         done = TRUE;
9942         if (cmailMsgLoaded) {
9943             ClearHighlights();
9944             flipView = WhiteOnMove(currentMove);
9945             if (moveType == GameUnfinished) flipView = !flipView;
9946             if (appData.debugMode)
9947               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9948         }
9949         break;
9950
9951       case EndOfFile:
9952         if (appData.debugMode)
9953           fprintf(debugFP, "Parser hit end of file\n");
9954         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9955           case MT_NONE:
9956           case MT_CHECK:
9957             break;
9958           case MT_CHECKMATE:
9959           case MT_STAINMATE:
9960             if (WhiteOnMove(currentMove)) {
9961                 GameEnds(BlackWins, "Black mates", GE_FILE);
9962             } else {
9963                 GameEnds(WhiteWins, "White mates", GE_FILE);
9964             }
9965             break;
9966           case MT_STALEMATE:
9967             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9968             break;
9969         }
9970         done = TRUE;
9971         break;
9972
9973       case MoveNumberOne:
9974         if (lastLoadGameStart == GNUChessGame) {
9975             /* GNUChessGames have numbers, but they aren't move numbers */
9976             if (appData.debugMode)
9977               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9978                       yy_text, (int) moveType);
9979             return LoadGameOneMove(EndOfFile); /* tail recursion */
9980         }
9981         /* else fall thru */
9982
9983       case XBoardGame:
9984       case GNUChessGame:
9985       case PGNTag:
9986         /* Reached start of next game in file */
9987         if (appData.debugMode)
9988           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9989         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9990           case MT_NONE:
9991           case MT_CHECK:
9992             break;
9993           case MT_CHECKMATE:
9994           case MT_STAINMATE:
9995             if (WhiteOnMove(currentMove)) {
9996                 GameEnds(BlackWins, "Black mates", GE_FILE);
9997             } else {
9998                 GameEnds(WhiteWins, "White mates", GE_FILE);
9999             }
10000             break;
10001           case MT_STALEMATE:
10002             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10003             break;
10004         }
10005         done = TRUE;
10006         break;
10007
10008       case PositionDiagram:     /* should not happen; ignore */
10009       case ElapsedTime:         /* ignore */
10010       case NAG:                 /* ignore */
10011         if (appData.debugMode)
10012           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10013                   yy_text, (int) moveType);
10014         return LoadGameOneMove(EndOfFile); /* tail recursion */
10015
10016       case IllegalMove:
10017         if (appData.testLegality) {
10018             if (appData.debugMode)
10019               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10020             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10021                     (forwardMostMove / 2) + 1,
10022                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10023             DisplayError(move, 0);
10024             done = TRUE;
10025         } else {
10026             if (appData.debugMode)
10027               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10028                       yy_text, currentMoveString);
10029             fromX = currentMoveString[0] - AAA;
10030             fromY = currentMoveString[1] - ONE;
10031             toX = currentMoveString[2] - AAA;
10032             toY = currentMoveString[3] - ONE;
10033             promoChar = currentMoveString[4];
10034         }
10035         break;
10036
10037       case AmbiguousMove:
10038         if (appData.debugMode)
10039           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10040         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10041                 (forwardMostMove / 2) + 1,
10042                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10043         DisplayError(move, 0);
10044         done = TRUE;
10045         break;
10046
10047       default:
10048       case ImpossibleMove:
10049         if (appData.debugMode)
10050           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10051         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10052                 (forwardMostMove / 2) + 1,
10053                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10054         DisplayError(move, 0);
10055         done = TRUE;
10056         break;
10057     }
10058
10059     if (done) {
10060         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10061             DrawPosition(FALSE, boards[currentMove]);
10062             DisplayBothClocks();
10063             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10064               DisplayComment(currentMove - 1, commentList[currentMove]);
10065         }
10066         (void) StopLoadGameTimer();
10067         gameFileFP = NULL;
10068         cmailOldMove = forwardMostMove;
10069         return FALSE;
10070     } else {
10071         /* currentMoveString is set as a side-effect of yylex */
10072
10073         thinkOutput[0] = NULLCHAR;
10074         MakeMove(fromX, fromY, toX, toY, promoChar);
10075         currentMove = forwardMostMove;
10076         return TRUE;
10077     }
10078 }
10079
10080 /* Load the nth game from the given file */
10081 int
10082 LoadGameFromFile(filename, n, title, useList)
10083      char *filename;
10084      int n;
10085      char *title;
10086      /*Boolean*/ int useList;
10087 {
10088     FILE *f;
10089     char buf[MSG_SIZ];
10090
10091     if (strcmp(filename, "-") == 0) {
10092         f = stdin;
10093         title = "stdin";
10094     } else {
10095         f = fopen(filename, "rb");
10096         if (f == NULL) {
10097           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10098             DisplayError(buf, errno);
10099             return FALSE;
10100         }
10101     }
10102     if (fseek(f, 0, 0) == -1) {
10103         /* f is not seekable; probably a pipe */
10104         useList = FALSE;
10105     }
10106     if (useList && n == 0) {
10107         int error = GameListBuild(f);
10108         if (error) {
10109             DisplayError(_("Cannot build game list"), error);
10110         } else if (!ListEmpty(&gameList) &&
10111                    ((ListGame *) gameList.tailPred)->number > 1) {
10112             GameListPopUp(f, title);
10113             return TRUE;
10114         }
10115         GameListDestroy();
10116         n = 1;
10117     }
10118     if (n == 0) n = 1;
10119     return LoadGame(f, n, title, FALSE);
10120 }
10121
10122
10123 void
10124 MakeRegisteredMove()
10125 {
10126     int fromX, fromY, toX, toY;
10127     char promoChar;
10128     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10129         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10130           case CMAIL_MOVE:
10131           case CMAIL_DRAW:
10132             if (appData.debugMode)
10133               fprintf(debugFP, "Restoring %s for game %d\n",
10134                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10135
10136             thinkOutput[0] = NULLCHAR;
10137             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10138             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10139             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10140             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10141             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10142             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10143             MakeMove(fromX, fromY, toX, toY, promoChar);
10144             ShowMove(fromX, fromY, toX, toY);
10145
10146             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10147               case MT_NONE:
10148               case MT_CHECK:
10149                 break;
10150
10151               case MT_CHECKMATE:
10152               case MT_STAINMATE:
10153                 if (WhiteOnMove(currentMove)) {
10154                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10155                 } else {
10156                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10157                 }
10158                 break;
10159
10160               case MT_STALEMATE:
10161                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10162                 break;
10163             }
10164
10165             break;
10166
10167           case CMAIL_RESIGN:
10168             if (WhiteOnMove(currentMove)) {
10169                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10170             } else {
10171                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10172             }
10173             break;
10174
10175           case CMAIL_ACCEPT:
10176             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10177             break;
10178
10179           default:
10180             break;
10181         }
10182     }
10183
10184     return;
10185 }
10186
10187 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10188 int
10189 CmailLoadGame(f, gameNumber, title, useList)
10190      FILE *f;
10191      int gameNumber;
10192      char *title;
10193      int useList;
10194 {
10195     int retVal;
10196
10197     if (gameNumber > nCmailGames) {
10198         DisplayError(_("No more games in this message"), 0);
10199         return FALSE;
10200     }
10201     if (f == lastLoadGameFP) {
10202         int offset = gameNumber - lastLoadGameNumber;
10203         if (offset == 0) {
10204             cmailMsg[0] = NULLCHAR;
10205             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10206                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10207                 nCmailMovesRegistered--;
10208             }
10209             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10210             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10211                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10212             }
10213         } else {
10214             if (! RegisterMove()) return FALSE;
10215         }
10216     }
10217
10218     retVal = LoadGame(f, gameNumber, title, useList);
10219
10220     /* Make move registered during previous look at this game, if any */
10221     MakeRegisteredMove();
10222
10223     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10224         commentList[currentMove]
10225           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10226         DisplayComment(currentMove - 1, commentList[currentMove]);
10227     }
10228
10229     return retVal;
10230 }
10231
10232 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10233 int
10234 ReloadGame(offset)
10235      int offset;
10236 {
10237     int gameNumber = lastLoadGameNumber + offset;
10238     if (lastLoadGameFP == NULL) {
10239         DisplayError(_("No game has been loaded yet"), 0);
10240         return FALSE;
10241     }
10242     if (gameNumber <= 0) {
10243         DisplayError(_("Can't back up any further"), 0);
10244         return FALSE;
10245     }
10246     if (cmailMsgLoaded) {
10247         return CmailLoadGame(lastLoadGameFP, gameNumber,
10248                              lastLoadGameTitle, lastLoadGameUseList);
10249     } else {
10250         return LoadGame(lastLoadGameFP, gameNumber,
10251                         lastLoadGameTitle, lastLoadGameUseList);
10252     }
10253 }
10254
10255
10256
10257 /* Load the nth game from open file f */
10258 int
10259 LoadGame(f, gameNumber, title, useList)
10260      FILE *f;
10261      int gameNumber;
10262      char *title;
10263      int useList;
10264 {
10265     ChessMove cm;
10266     char buf[MSG_SIZ];
10267     int gn = gameNumber;
10268     ListGame *lg = NULL;
10269     int numPGNTags = 0;
10270     int err;
10271     GameMode oldGameMode;
10272     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10273
10274     if (appData.debugMode)
10275         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10276
10277     if (gameMode == Training )
10278         SetTrainingModeOff();
10279
10280     oldGameMode = gameMode;
10281     if (gameMode != BeginningOfGame) {
10282       Reset(FALSE, TRUE);
10283     }
10284
10285     gameFileFP = f;
10286     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10287         fclose(lastLoadGameFP);
10288     }
10289
10290     if (useList) {
10291         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10292
10293         if (lg) {
10294             fseek(f, lg->offset, 0);
10295             GameListHighlight(gameNumber);
10296             gn = 1;
10297         }
10298         else {
10299             DisplayError(_("Game number out of range"), 0);
10300             return FALSE;
10301         }
10302     } else {
10303         GameListDestroy();
10304         if (fseek(f, 0, 0) == -1) {
10305             if (f == lastLoadGameFP ?
10306                 gameNumber == lastLoadGameNumber + 1 :
10307                 gameNumber == 1) {
10308                 gn = 1;
10309             } else {
10310                 DisplayError(_("Can't seek on game file"), 0);
10311                 return FALSE;
10312             }
10313         }
10314     }
10315     lastLoadGameFP = f;
10316     lastLoadGameNumber = gameNumber;
10317     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10318     lastLoadGameUseList = useList;
10319
10320     yynewfile(f);
10321
10322     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10323       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10324                 lg->gameInfo.black);
10325             DisplayTitle(buf);
10326     } else if (*title != NULLCHAR) {
10327         if (gameNumber > 1) {
10328           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10329             DisplayTitle(buf);
10330         } else {
10331             DisplayTitle(title);
10332         }
10333     }
10334
10335     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10336         gameMode = PlayFromGameFile;
10337         ModeHighlight();
10338     }
10339
10340     currentMove = forwardMostMove = backwardMostMove = 0;
10341     CopyBoard(boards[0], initialPosition);
10342     StopClocks();
10343
10344     /*
10345      * Skip the first gn-1 games in the file.
10346      * Also skip over anything that precedes an identifiable
10347      * start of game marker, to avoid being confused by
10348      * garbage at the start of the file.  Currently
10349      * recognized start of game markers are the move number "1",
10350      * the pattern "gnuchess .* game", the pattern
10351      * "^[#;%] [^ ]* game file", and a PGN tag block.
10352      * A game that starts with one of the latter two patterns
10353      * will also have a move number 1, possibly
10354      * following a position diagram.
10355      * 5-4-02: Let's try being more lenient and allowing a game to
10356      * start with an unnumbered move.  Does that break anything?
10357      */
10358     cm = lastLoadGameStart = EndOfFile;
10359     while (gn > 0) {
10360         yyboardindex = forwardMostMove;
10361         cm = (ChessMove) Myylex();
10362         switch (cm) {
10363           case EndOfFile:
10364             if (cmailMsgLoaded) {
10365                 nCmailGames = CMAIL_MAX_GAMES - gn;
10366             } else {
10367                 Reset(TRUE, TRUE);
10368                 DisplayError(_("Game not found in file"), 0);
10369             }
10370             return FALSE;
10371
10372           case GNUChessGame:
10373           case XBoardGame:
10374             gn--;
10375             lastLoadGameStart = cm;
10376             break;
10377
10378           case MoveNumberOne:
10379             switch (lastLoadGameStart) {
10380               case GNUChessGame:
10381               case XBoardGame:
10382               case PGNTag:
10383                 break;
10384               case MoveNumberOne:
10385               case EndOfFile:
10386                 gn--;           /* count this game */
10387                 lastLoadGameStart = cm;
10388                 break;
10389               default:
10390                 /* impossible */
10391                 break;
10392             }
10393             break;
10394
10395           case PGNTag:
10396             switch (lastLoadGameStart) {
10397               case GNUChessGame:
10398               case PGNTag:
10399               case MoveNumberOne:
10400               case EndOfFile:
10401                 gn--;           /* count this game */
10402                 lastLoadGameStart = cm;
10403                 break;
10404               case XBoardGame:
10405                 lastLoadGameStart = cm; /* game counted already */
10406                 break;
10407               default:
10408                 /* impossible */
10409                 break;
10410             }
10411             if (gn > 0) {
10412                 do {
10413                     yyboardindex = forwardMostMove;
10414                     cm = (ChessMove) Myylex();
10415                 } while (cm == PGNTag || cm == Comment);
10416             }
10417             break;
10418
10419           case WhiteWins:
10420           case BlackWins:
10421           case GameIsDrawn:
10422             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10423                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10424                     != CMAIL_OLD_RESULT) {
10425                     nCmailResults ++ ;
10426                     cmailResult[  CMAIL_MAX_GAMES
10427                                 - gn - 1] = CMAIL_OLD_RESULT;
10428                 }
10429             }
10430             break;
10431
10432           case NormalMove:
10433             /* Only a NormalMove can be at the start of a game
10434              * without a position diagram. */
10435             if (lastLoadGameStart == EndOfFile ) {
10436               gn--;
10437               lastLoadGameStart = MoveNumberOne;
10438             }
10439             break;
10440
10441           default:
10442             break;
10443         }
10444     }
10445
10446     if (appData.debugMode)
10447       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10448
10449     if (cm == XBoardGame) {
10450         /* Skip any header junk before position diagram and/or move 1 */
10451         for (;;) {
10452             yyboardindex = forwardMostMove;
10453             cm = (ChessMove) Myylex();
10454
10455             if (cm == EndOfFile ||
10456                 cm == GNUChessGame || cm == XBoardGame) {
10457                 /* Empty game; pretend end-of-file and handle later */
10458                 cm = EndOfFile;
10459                 break;
10460             }
10461
10462             if (cm == MoveNumberOne || cm == PositionDiagram ||
10463                 cm == PGNTag || cm == Comment)
10464               break;
10465         }
10466     } else if (cm == GNUChessGame) {
10467         if (gameInfo.event != NULL) {
10468             free(gameInfo.event);
10469         }
10470         gameInfo.event = StrSave(yy_text);
10471     }
10472
10473     startedFromSetupPosition = FALSE;
10474     while (cm == PGNTag) {
10475         if (appData.debugMode)
10476           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10477         err = ParsePGNTag(yy_text, &gameInfo);
10478         if (!err) numPGNTags++;
10479
10480         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10481         if(gameInfo.variant != oldVariant) {
10482             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10483             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10484             InitPosition(TRUE);
10485             oldVariant = gameInfo.variant;
10486             if (appData.debugMode)
10487               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10488         }
10489
10490
10491         if (gameInfo.fen != NULL) {
10492           Board initial_position;
10493           startedFromSetupPosition = TRUE;
10494           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10495             Reset(TRUE, TRUE);
10496             DisplayError(_("Bad FEN position in file"), 0);
10497             return FALSE;
10498           }
10499           CopyBoard(boards[0], initial_position);
10500           if (blackPlaysFirst) {
10501             currentMove = forwardMostMove = backwardMostMove = 1;
10502             CopyBoard(boards[1], initial_position);
10503             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10504             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10505             timeRemaining[0][1] = whiteTimeRemaining;
10506             timeRemaining[1][1] = blackTimeRemaining;
10507             if (commentList[0] != NULL) {
10508               commentList[1] = commentList[0];
10509               commentList[0] = NULL;
10510             }
10511           } else {
10512             currentMove = forwardMostMove = backwardMostMove = 0;
10513           }
10514           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10515           {   int i;
10516               initialRulePlies = FENrulePlies;
10517               for( i=0; i< nrCastlingRights; i++ )
10518                   initialRights[i] = initial_position[CASTLING][i];
10519           }
10520           yyboardindex = forwardMostMove;
10521           free(gameInfo.fen);
10522           gameInfo.fen = NULL;
10523         }
10524
10525         yyboardindex = forwardMostMove;
10526         cm = (ChessMove) Myylex();
10527
10528         /* Handle comments interspersed among the tags */
10529         while (cm == Comment) {
10530             char *p;
10531             if (appData.debugMode)
10532               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10533             p = yy_text;
10534             AppendComment(currentMove, p, FALSE);
10535             yyboardindex = forwardMostMove;
10536             cm = (ChessMove) Myylex();
10537         }
10538     }
10539
10540     /* don't rely on existence of Event tag since if game was
10541      * pasted from clipboard the Event tag may not exist
10542      */
10543     if (numPGNTags > 0){
10544         char *tags;
10545         if (gameInfo.variant == VariantNormal) {
10546           VariantClass v = StringToVariant(gameInfo.event);
10547           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10548           if(v < VariantShogi) gameInfo.variant = v;
10549         }
10550         if (!matchMode) {
10551           if( appData.autoDisplayTags ) {
10552             tags = PGNTags(&gameInfo);
10553             TagsPopUp(tags, CmailMsg());
10554             free(tags);
10555           }
10556         }
10557     } else {
10558         /* Make something up, but don't display it now */
10559         SetGameInfo();
10560         TagsPopDown();
10561     }
10562
10563     if (cm == PositionDiagram) {
10564         int i, j;
10565         char *p;
10566         Board initial_position;
10567
10568         if (appData.debugMode)
10569           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10570
10571         if (!startedFromSetupPosition) {
10572             p = yy_text;
10573             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10574               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10575                 switch (*p) {
10576                   case '{':
10577                   case '[':
10578                   case '-':
10579                   case ' ':
10580                   case '\t':
10581                   case '\n':
10582                   case '\r':
10583                     break;
10584                   default:
10585                     initial_position[i][j++] = CharToPiece(*p);
10586                     break;
10587                 }
10588             while (*p == ' ' || *p == '\t' ||
10589                    *p == '\n' || *p == '\r') p++;
10590
10591             if (strncmp(p, "black", strlen("black"))==0)
10592               blackPlaysFirst = TRUE;
10593             else
10594               blackPlaysFirst = FALSE;
10595             startedFromSetupPosition = TRUE;
10596
10597             CopyBoard(boards[0], initial_position);
10598             if (blackPlaysFirst) {
10599                 currentMove = forwardMostMove = backwardMostMove = 1;
10600                 CopyBoard(boards[1], initial_position);
10601                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10602                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10603                 timeRemaining[0][1] = whiteTimeRemaining;
10604                 timeRemaining[1][1] = blackTimeRemaining;
10605                 if (commentList[0] != NULL) {
10606                     commentList[1] = commentList[0];
10607                     commentList[0] = NULL;
10608                 }
10609             } else {
10610                 currentMove = forwardMostMove = backwardMostMove = 0;
10611             }
10612         }
10613         yyboardindex = forwardMostMove;
10614         cm = (ChessMove) Myylex();
10615     }
10616
10617     if (first.pr == NoProc) {
10618         StartChessProgram(&first);
10619     }
10620     InitChessProgram(&first, FALSE);
10621     SendToProgram("force\n", &first);
10622     if (startedFromSetupPosition) {
10623         SendBoard(&first, forwardMostMove);
10624     if (appData.debugMode) {
10625         fprintf(debugFP, "Load Game\n");
10626     }
10627         DisplayBothClocks();
10628     }
10629
10630     /* [HGM] server: flag to write setup moves in broadcast file as one */
10631     loadFlag = appData.suppressLoadMoves;
10632
10633     while (cm == Comment) {
10634         char *p;
10635         if (appData.debugMode)
10636           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10637         p = yy_text;
10638         AppendComment(currentMove, p, FALSE);
10639         yyboardindex = forwardMostMove;
10640         cm = (ChessMove) Myylex();
10641     }
10642
10643     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10644         cm == WhiteWins || cm == BlackWins ||
10645         cm == GameIsDrawn || cm == GameUnfinished) {
10646         DisplayMessage("", _("No moves in game"));
10647         if (cmailMsgLoaded) {
10648             if (appData.debugMode)
10649               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10650             ClearHighlights();
10651             flipView = FALSE;
10652         }
10653         DrawPosition(FALSE, boards[currentMove]);
10654         DisplayBothClocks();
10655         gameMode = EditGame;
10656         ModeHighlight();
10657         gameFileFP = NULL;
10658         cmailOldMove = 0;
10659         return TRUE;
10660     }
10661
10662     // [HGM] PV info: routine tests if comment empty
10663     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10664         DisplayComment(currentMove - 1, commentList[currentMove]);
10665     }
10666     if (!matchMode && appData.timeDelay != 0)
10667       DrawPosition(FALSE, boards[currentMove]);
10668
10669     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10670       programStats.ok_to_send = 1;
10671     }
10672
10673     /* if the first token after the PGN tags is a move
10674      * and not move number 1, retrieve it from the parser
10675      */
10676     if (cm != MoveNumberOne)
10677         LoadGameOneMove(cm);
10678
10679     /* load the remaining moves from the file */
10680     while (LoadGameOneMove(EndOfFile)) {
10681       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10682       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10683     }
10684
10685     /* rewind to the start of the game */
10686     currentMove = backwardMostMove;
10687
10688     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10689
10690     if (oldGameMode == AnalyzeFile ||
10691         oldGameMode == AnalyzeMode) {
10692       AnalyzeFileEvent();
10693     }
10694
10695     if (matchMode || appData.timeDelay == 0) {
10696       ToEndEvent();
10697       gameMode = EditGame;
10698       ModeHighlight();
10699     } else if (appData.timeDelay > 0) {
10700       AutoPlayGameLoop();
10701     }
10702
10703     if (appData.debugMode)
10704         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10705
10706     loadFlag = 0; /* [HGM] true game starts */
10707     return TRUE;
10708 }
10709
10710 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10711 int
10712 ReloadPosition(offset)
10713      int offset;
10714 {
10715     int positionNumber = lastLoadPositionNumber + offset;
10716     if (lastLoadPositionFP == NULL) {
10717         DisplayError(_("No position has been loaded yet"), 0);
10718         return FALSE;
10719     }
10720     if (positionNumber <= 0) {
10721         DisplayError(_("Can't back up any further"), 0);
10722         return FALSE;
10723     }
10724     return LoadPosition(lastLoadPositionFP, positionNumber,
10725                         lastLoadPositionTitle);
10726 }
10727
10728 /* Load the nth position from the given file */
10729 int
10730 LoadPositionFromFile(filename, n, title)
10731      char *filename;
10732      int n;
10733      char *title;
10734 {
10735     FILE *f;
10736     char buf[MSG_SIZ];
10737
10738     if (strcmp(filename, "-") == 0) {
10739         return LoadPosition(stdin, n, "stdin");
10740     } else {
10741         f = fopen(filename, "rb");
10742         if (f == NULL) {
10743             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10744             DisplayError(buf, errno);
10745             return FALSE;
10746         } else {
10747             return LoadPosition(f, n, title);
10748         }
10749     }
10750 }
10751
10752 /* Load the nth position from the given open file, and close it */
10753 int
10754 LoadPosition(f, positionNumber, title)
10755      FILE *f;
10756      int positionNumber;
10757      char *title;
10758 {
10759     char *p, line[MSG_SIZ];
10760     Board initial_position;
10761     int i, j, fenMode, pn;
10762
10763     if (gameMode == Training )
10764         SetTrainingModeOff();
10765
10766     if (gameMode != BeginningOfGame) {
10767         Reset(FALSE, TRUE);
10768     }
10769     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10770         fclose(lastLoadPositionFP);
10771     }
10772     if (positionNumber == 0) positionNumber = 1;
10773     lastLoadPositionFP = f;
10774     lastLoadPositionNumber = positionNumber;
10775     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10776     if (first.pr == NoProc) {
10777       StartChessProgram(&first);
10778       InitChessProgram(&first, FALSE);
10779     }
10780     pn = positionNumber;
10781     if (positionNumber < 0) {
10782         /* Negative position number means to seek to that byte offset */
10783         if (fseek(f, -positionNumber, 0) == -1) {
10784             DisplayError(_("Can't seek on position file"), 0);
10785             return FALSE;
10786         };
10787         pn = 1;
10788     } else {
10789         if (fseek(f, 0, 0) == -1) {
10790             if (f == lastLoadPositionFP ?
10791                 positionNumber == lastLoadPositionNumber + 1 :
10792                 positionNumber == 1) {
10793                 pn = 1;
10794             } else {
10795                 DisplayError(_("Can't seek on position file"), 0);
10796                 return FALSE;
10797             }
10798         }
10799     }
10800     /* See if this file is FEN or old-style xboard */
10801     if (fgets(line, MSG_SIZ, f) == NULL) {
10802         DisplayError(_("Position not found in file"), 0);
10803         return FALSE;
10804     }
10805     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10806     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10807
10808     if (pn >= 2) {
10809         if (fenMode || line[0] == '#') pn--;
10810         while (pn > 0) {
10811             /* skip positions before number pn */
10812             if (fgets(line, MSG_SIZ, f) == NULL) {
10813                 Reset(TRUE, TRUE);
10814                 DisplayError(_("Position not found in file"), 0);
10815                 return FALSE;
10816             }
10817             if (fenMode || line[0] == '#') pn--;
10818         }
10819     }
10820
10821     if (fenMode) {
10822         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10823             DisplayError(_("Bad FEN position in file"), 0);
10824             return FALSE;
10825         }
10826     } else {
10827         (void) fgets(line, MSG_SIZ, f);
10828         (void) fgets(line, MSG_SIZ, f);
10829
10830         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10831             (void) fgets(line, MSG_SIZ, f);
10832             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10833                 if (*p == ' ')
10834                   continue;
10835                 initial_position[i][j++] = CharToPiece(*p);
10836             }
10837         }
10838
10839         blackPlaysFirst = FALSE;
10840         if (!feof(f)) {
10841             (void) fgets(line, MSG_SIZ, f);
10842             if (strncmp(line, "black", strlen("black"))==0)
10843               blackPlaysFirst = TRUE;
10844         }
10845     }
10846     startedFromSetupPosition = TRUE;
10847
10848     SendToProgram("force\n", &first);
10849     CopyBoard(boards[0], initial_position);
10850     if (blackPlaysFirst) {
10851         currentMove = forwardMostMove = backwardMostMove = 1;
10852         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10853         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10854         CopyBoard(boards[1], initial_position);
10855         DisplayMessage("", _("Black to play"));
10856     } else {
10857         currentMove = forwardMostMove = backwardMostMove = 0;
10858         DisplayMessage("", _("White to play"));
10859     }
10860     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10861     SendBoard(&first, forwardMostMove);
10862     if (appData.debugMode) {
10863 int i, j;
10864   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10865   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10866         fprintf(debugFP, "Load Position\n");
10867     }
10868
10869     if (positionNumber > 1) {
10870       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10871         DisplayTitle(line);
10872     } else {
10873         DisplayTitle(title);
10874     }
10875     gameMode = EditGame;
10876     ModeHighlight();
10877     ResetClocks();
10878     timeRemaining[0][1] = whiteTimeRemaining;
10879     timeRemaining[1][1] = blackTimeRemaining;
10880     DrawPosition(FALSE, boards[currentMove]);
10881
10882     return TRUE;
10883 }
10884
10885
10886 void
10887 CopyPlayerNameIntoFileName(dest, src)
10888      char **dest, *src;
10889 {
10890     while (*src != NULLCHAR && *src != ',') {
10891         if (*src == ' ') {
10892             *(*dest)++ = '_';
10893             src++;
10894         } else {
10895             *(*dest)++ = *src++;
10896         }
10897     }
10898 }
10899
10900 char *DefaultFileName(ext)
10901      char *ext;
10902 {
10903     static char def[MSG_SIZ];
10904     char *p;
10905
10906     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10907         p = def;
10908         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10909         *p++ = '-';
10910         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10911         *p++ = '.';
10912         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10913     } else {
10914         def[0] = NULLCHAR;
10915     }
10916     return def;
10917 }
10918
10919 /* Save the current game to the given file */
10920 int
10921 SaveGameToFile(filename, append)
10922      char *filename;
10923      int append;
10924 {
10925     FILE *f;
10926     char buf[MSG_SIZ];
10927
10928     if (strcmp(filename, "-") == 0) {
10929         return SaveGame(stdout, 0, NULL);
10930     } else {
10931         f = fopen(filename, append ? "a" : "w");
10932         if (f == NULL) {
10933             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10934             DisplayError(buf, errno);
10935             return FALSE;
10936         } else {
10937             return SaveGame(f, 0, NULL);
10938         }
10939     }
10940 }
10941
10942 char *
10943 SavePart(str)
10944      char *str;
10945 {
10946     static char buf[MSG_SIZ];
10947     char *p;
10948
10949     p = strchr(str, ' ');
10950     if (p == NULL) return str;
10951     strncpy(buf, str, p - str);
10952     buf[p - str] = NULLCHAR;
10953     return buf;
10954 }
10955
10956 #define PGN_MAX_LINE 75
10957
10958 #define PGN_SIDE_WHITE  0
10959 #define PGN_SIDE_BLACK  1
10960
10961 /* [AS] */
10962 static int FindFirstMoveOutOfBook( int side )
10963 {
10964     int result = -1;
10965
10966     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10967         int index = backwardMostMove;
10968         int has_book_hit = 0;
10969
10970         if( (index % 2) != side ) {
10971             index++;
10972         }
10973
10974         while( index < forwardMostMove ) {
10975             /* Check to see if engine is in book */
10976             int depth = pvInfoList[index].depth;
10977             int score = pvInfoList[index].score;
10978             int in_book = 0;
10979
10980             if( depth <= 2 ) {
10981                 in_book = 1;
10982             }
10983             else if( score == 0 && depth == 63 ) {
10984                 in_book = 1; /* Zappa */
10985             }
10986             else if( score == 2 && depth == 99 ) {
10987                 in_book = 1; /* Abrok */
10988             }
10989
10990             has_book_hit += in_book;
10991
10992             if( ! in_book ) {
10993                 result = index;
10994
10995                 break;
10996             }
10997
10998             index += 2;
10999         }
11000     }
11001
11002     return result;
11003 }
11004
11005 /* [AS] */
11006 void GetOutOfBookInfo( char * buf )
11007 {
11008     int oob[2];
11009     int i;
11010     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11011
11012     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11013     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11014
11015     *buf = '\0';
11016
11017     if( oob[0] >= 0 || oob[1] >= 0 ) {
11018         for( i=0; i<2; i++ ) {
11019             int idx = oob[i];
11020
11021             if( idx >= 0 ) {
11022                 if( i > 0 && oob[0] >= 0 ) {
11023                     strcat( buf, "   " );
11024                 }
11025
11026                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11027                 sprintf( buf+strlen(buf), "%s%.2f",
11028                     pvInfoList[idx].score >= 0 ? "+" : "",
11029                     pvInfoList[idx].score / 100.0 );
11030             }
11031         }
11032     }
11033 }
11034
11035 /* Save game in PGN style and close the file */
11036 int
11037 SaveGamePGN(f)
11038      FILE *f;
11039 {
11040     int i, offset, linelen, newblock;
11041     time_t tm;
11042 //    char *movetext;
11043     char numtext[32];
11044     int movelen, numlen, blank;
11045     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11046
11047     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11048
11049     tm = time((time_t *) NULL);
11050
11051     PrintPGNTags(f, &gameInfo);
11052
11053     if (backwardMostMove > 0 || startedFromSetupPosition) {
11054         char *fen = PositionToFEN(backwardMostMove, NULL);
11055         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11056         fprintf(f, "\n{--------------\n");
11057         PrintPosition(f, backwardMostMove);
11058         fprintf(f, "--------------}\n");
11059         free(fen);
11060     }
11061     else {
11062         /* [AS] Out of book annotation */
11063         if( appData.saveOutOfBookInfo ) {
11064             char buf[64];
11065
11066             GetOutOfBookInfo( buf );
11067
11068             if( buf[0] != '\0' ) {
11069                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11070             }
11071         }
11072
11073         fprintf(f, "\n");
11074     }
11075
11076     i = backwardMostMove;
11077     linelen = 0;
11078     newblock = TRUE;
11079
11080     while (i < forwardMostMove) {
11081         /* Print comments preceding this move */
11082         if (commentList[i] != NULL) {
11083             if (linelen > 0) fprintf(f, "\n");
11084             fprintf(f, "%s", commentList[i]);
11085             linelen = 0;
11086             newblock = TRUE;
11087         }
11088
11089         /* Format move number */
11090         if ((i % 2) == 0)
11091           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11092         else
11093           if (newblock)
11094             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11095           else
11096             numtext[0] = NULLCHAR;
11097
11098         numlen = strlen(numtext);
11099         newblock = FALSE;
11100
11101         /* Print move number */
11102         blank = linelen > 0 && numlen > 0;
11103         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11104             fprintf(f, "\n");
11105             linelen = 0;
11106             blank = 0;
11107         }
11108         if (blank) {
11109             fprintf(f, " ");
11110             linelen++;
11111         }
11112         fprintf(f, "%s", numtext);
11113         linelen += numlen;
11114
11115         /* Get move */
11116         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11117         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11118
11119         /* Print move */
11120         blank = linelen > 0 && movelen > 0;
11121         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11122             fprintf(f, "\n");
11123             linelen = 0;
11124             blank = 0;
11125         }
11126         if (blank) {
11127             fprintf(f, " ");
11128             linelen++;
11129         }
11130         fprintf(f, "%s", move_buffer);
11131         linelen += movelen;
11132
11133         /* [AS] Add PV info if present */
11134         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11135             /* [HGM] add time */
11136             char buf[MSG_SIZ]; int seconds;
11137
11138             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11139
11140             if( seconds <= 0)
11141               buf[0] = 0;
11142             else
11143               if( seconds < 30 )
11144                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11145               else
11146                 {
11147                   seconds = (seconds + 4)/10; // round to full seconds
11148                   if( seconds < 60 )
11149                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11150                   else
11151                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11152                 }
11153
11154             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11155                       pvInfoList[i].score >= 0 ? "+" : "",
11156                       pvInfoList[i].score / 100.0,
11157                       pvInfoList[i].depth,
11158                       buf );
11159
11160             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11161
11162             /* Print score/depth */
11163             blank = linelen > 0 && movelen > 0;
11164             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11165                 fprintf(f, "\n");
11166                 linelen = 0;
11167                 blank = 0;
11168             }
11169             if (blank) {
11170                 fprintf(f, " ");
11171                 linelen++;
11172             }
11173             fprintf(f, "%s", move_buffer);
11174             linelen += movelen;
11175         }
11176
11177         i++;
11178     }
11179
11180     /* Start a new line */
11181     if (linelen > 0) fprintf(f, "\n");
11182
11183     /* Print comments after last move */
11184     if (commentList[i] != NULL) {
11185         fprintf(f, "%s\n", commentList[i]);
11186     }
11187
11188     /* Print result */
11189     if (gameInfo.resultDetails != NULL &&
11190         gameInfo.resultDetails[0] != NULLCHAR) {
11191         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11192                 PGNResult(gameInfo.result));
11193     } else {
11194         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11195     }
11196
11197     fclose(f);
11198     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11199     return TRUE;
11200 }
11201
11202 /* Save game in old style and close the file */
11203 int
11204 SaveGameOldStyle(f)
11205      FILE *f;
11206 {
11207     int i, offset;
11208     time_t tm;
11209
11210     tm = time((time_t *) NULL);
11211
11212     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11213     PrintOpponents(f);
11214
11215     if (backwardMostMove > 0 || startedFromSetupPosition) {
11216         fprintf(f, "\n[--------------\n");
11217         PrintPosition(f, backwardMostMove);
11218         fprintf(f, "--------------]\n");
11219     } else {
11220         fprintf(f, "\n");
11221     }
11222
11223     i = backwardMostMove;
11224     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11225
11226     while (i < forwardMostMove) {
11227         if (commentList[i] != NULL) {
11228             fprintf(f, "[%s]\n", commentList[i]);
11229         }
11230
11231         if ((i % 2) == 1) {
11232             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11233             i++;
11234         } else {
11235             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11236             i++;
11237             if (commentList[i] != NULL) {
11238                 fprintf(f, "\n");
11239                 continue;
11240             }
11241             if (i >= forwardMostMove) {
11242                 fprintf(f, "\n");
11243                 break;
11244             }
11245             fprintf(f, "%s\n", parseList[i]);
11246             i++;
11247         }
11248     }
11249
11250     if (commentList[i] != NULL) {
11251         fprintf(f, "[%s]\n", commentList[i]);
11252     }
11253
11254     /* This isn't really the old style, but it's close enough */
11255     if (gameInfo.resultDetails != NULL &&
11256         gameInfo.resultDetails[0] != NULLCHAR) {
11257         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11258                 gameInfo.resultDetails);
11259     } else {
11260         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11261     }
11262
11263     fclose(f);
11264     return TRUE;
11265 }
11266
11267 /* Save the current game to open file f and close the file */
11268 int
11269 SaveGame(f, dummy, dummy2)
11270      FILE *f;
11271      int dummy;
11272      char *dummy2;
11273 {
11274     if (gameMode == EditPosition) EditPositionDone(TRUE);
11275     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11276     if (appData.oldSaveStyle)
11277       return SaveGameOldStyle(f);
11278     else
11279       return SaveGamePGN(f);
11280 }
11281
11282 /* Save the current position to the given file */
11283 int
11284 SavePositionToFile(filename)
11285      char *filename;
11286 {
11287     FILE *f;
11288     char buf[MSG_SIZ];
11289
11290     if (strcmp(filename, "-") == 0) {
11291         return SavePosition(stdout, 0, NULL);
11292     } else {
11293         f = fopen(filename, "a");
11294         if (f == NULL) {
11295             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11296             DisplayError(buf, errno);
11297             return FALSE;
11298         } else {
11299             SavePosition(f, 0, NULL);
11300             return TRUE;
11301         }
11302     }
11303 }
11304
11305 /* Save the current position to the given open file and close the file */
11306 int
11307 SavePosition(f, dummy, dummy2)
11308      FILE *f;
11309      int dummy;
11310      char *dummy2;
11311 {
11312     time_t tm;
11313     char *fen;
11314
11315     if (gameMode == EditPosition) EditPositionDone(TRUE);
11316     if (appData.oldSaveStyle) {
11317         tm = time((time_t *) NULL);
11318
11319         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11320         PrintOpponents(f);
11321         fprintf(f, "[--------------\n");
11322         PrintPosition(f, currentMove);
11323         fprintf(f, "--------------]\n");
11324     } else {
11325         fen = PositionToFEN(currentMove, NULL);
11326         fprintf(f, "%s\n", fen);
11327         free(fen);
11328     }
11329     fclose(f);
11330     return TRUE;
11331 }
11332
11333 void
11334 ReloadCmailMsgEvent(unregister)
11335      int unregister;
11336 {
11337 #if !WIN32
11338     static char *inFilename = NULL;
11339     static char *outFilename;
11340     int i;
11341     struct stat inbuf, outbuf;
11342     int status;
11343
11344     /* Any registered moves are unregistered if unregister is set, */
11345     /* i.e. invoked by the signal handler */
11346     if (unregister) {
11347         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11348             cmailMoveRegistered[i] = FALSE;
11349             if (cmailCommentList[i] != NULL) {
11350                 free(cmailCommentList[i]);
11351                 cmailCommentList[i] = NULL;
11352             }
11353         }
11354         nCmailMovesRegistered = 0;
11355     }
11356
11357     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11358         cmailResult[i] = CMAIL_NOT_RESULT;
11359     }
11360     nCmailResults = 0;
11361
11362     if (inFilename == NULL) {
11363         /* Because the filenames are static they only get malloced once  */
11364         /* and they never get freed                                      */
11365         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11366         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11367
11368         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11369         sprintf(outFilename, "%s.out", appData.cmailGameName);
11370     }
11371
11372     status = stat(outFilename, &outbuf);
11373     if (status < 0) {
11374         cmailMailedMove = FALSE;
11375     } else {
11376         status = stat(inFilename, &inbuf);
11377         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11378     }
11379
11380     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11381        counts the games, notes how each one terminated, etc.
11382
11383        It would be nice to remove this kludge and instead gather all
11384        the information while building the game list.  (And to keep it
11385        in the game list nodes instead of having a bunch of fixed-size
11386        parallel arrays.)  Note this will require getting each game's
11387        termination from the PGN tags, as the game list builder does
11388        not process the game moves.  --mann
11389        */
11390     cmailMsgLoaded = TRUE;
11391     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11392
11393     /* Load first game in the file or popup game menu */
11394     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11395
11396 #endif /* !WIN32 */
11397     return;
11398 }
11399
11400 int
11401 RegisterMove()
11402 {
11403     FILE *f;
11404     char string[MSG_SIZ];
11405
11406     if (   cmailMailedMove
11407         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11408         return TRUE;            /* Allow free viewing  */
11409     }
11410
11411     /* Unregister move to ensure that we don't leave RegisterMove        */
11412     /* with the move registered when the conditions for registering no   */
11413     /* longer hold                                                       */
11414     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11415         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11416         nCmailMovesRegistered --;
11417
11418         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11419           {
11420               free(cmailCommentList[lastLoadGameNumber - 1]);
11421               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11422           }
11423     }
11424
11425     if (cmailOldMove == -1) {
11426         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11427         return FALSE;
11428     }
11429
11430     if (currentMove > cmailOldMove + 1) {
11431         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11432         return FALSE;
11433     }
11434
11435     if (currentMove < cmailOldMove) {
11436         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11437         return FALSE;
11438     }
11439
11440     if (forwardMostMove > currentMove) {
11441         /* Silently truncate extra moves */
11442         TruncateGame();
11443     }
11444
11445     if (   (currentMove == cmailOldMove + 1)
11446         || (   (currentMove == cmailOldMove)
11447             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11448                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11449         if (gameInfo.result != GameUnfinished) {
11450             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11451         }
11452
11453         if (commentList[currentMove] != NULL) {
11454             cmailCommentList[lastLoadGameNumber - 1]
11455               = StrSave(commentList[currentMove]);
11456         }
11457         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11458
11459         if (appData.debugMode)
11460           fprintf(debugFP, "Saving %s for game %d\n",
11461                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11462
11463         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11464
11465         f = fopen(string, "w");
11466         if (appData.oldSaveStyle) {
11467             SaveGameOldStyle(f); /* also closes the file */
11468
11469             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11470             f = fopen(string, "w");
11471             SavePosition(f, 0, NULL); /* also closes the file */
11472         } else {
11473             fprintf(f, "{--------------\n");
11474             PrintPosition(f, currentMove);
11475             fprintf(f, "--------------}\n\n");
11476
11477             SaveGame(f, 0, NULL); /* also closes the file*/
11478         }
11479
11480         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11481         nCmailMovesRegistered ++;
11482     } else if (nCmailGames == 1) {
11483         DisplayError(_("You have not made a move yet"), 0);
11484         return FALSE;
11485     }
11486
11487     return TRUE;
11488 }
11489
11490 void
11491 MailMoveEvent()
11492 {
11493 #if !WIN32
11494     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11495     FILE *commandOutput;
11496     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11497     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11498     int nBuffers;
11499     int i;
11500     int archived;
11501     char *arcDir;
11502
11503     if (! cmailMsgLoaded) {
11504         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11505         return;
11506     }
11507
11508     if (nCmailGames == nCmailResults) {
11509         DisplayError(_("No unfinished games"), 0);
11510         return;
11511     }
11512
11513 #if CMAIL_PROHIBIT_REMAIL
11514     if (cmailMailedMove) {
11515       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);
11516         DisplayError(msg, 0);
11517         return;
11518     }
11519 #endif
11520
11521     if (! (cmailMailedMove || RegisterMove())) return;
11522
11523     if (   cmailMailedMove
11524         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11525       snprintf(string, MSG_SIZ, partCommandString,
11526                appData.debugMode ? " -v" : "", appData.cmailGameName);
11527         commandOutput = popen(string, "r");
11528
11529         if (commandOutput == NULL) {
11530             DisplayError(_("Failed to invoke cmail"), 0);
11531         } else {
11532             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11533                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11534             }
11535             if (nBuffers > 1) {
11536                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11537                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11538                 nBytes = MSG_SIZ - 1;
11539             } else {
11540                 (void) memcpy(msg, buffer, nBytes);
11541             }
11542             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11543
11544             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11545                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11546
11547                 archived = TRUE;
11548                 for (i = 0; i < nCmailGames; i ++) {
11549                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11550                         archived = FALSE;
11551                     }
11552                 }
11553                 if (   archived
11554                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11555                         != NULL)) {
11556                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11557                            arcDir,
11558                            appData.cmailGameName,
11559                            gameInfo.date);
11560                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11561                     cmailMsgLoaded = FALSE;
11562                 }
11563             }
11564
11565             DisplayInformation(msg);
11566             pclose(commandOutput);
11567         }
11568     } else {
11569         if ((*cmailMsg) != '\0') {
11570             DisplayInformation(cmailMsg);
11571         }
11572     }
11573
11574     return;
11575 #endif /* !WIN32 */
11576 }
11577
11578 char *
11579 CmailMsg()
11580 {
11581 #if WIN32
11582     return NULL;
11583 #else
11584     int  prependComma = 0;
11585     char number[5];
11586     char string[MSG_SIZ];       /* Space for game-list */
11587     int  i;
11588
11589     if (!cmailMsgLoaded) return "";
11590
11591     if (cmailMailedMove) {
11592       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11593     } else {
11594         /* Create a list of games left */
11595       snprintf(string, MSG_SIZ, "[");
11596         for (i = 0; i < nCmailGames; i ++) {
11597             if (! (   cmailMoveRegistered[i]
11598                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11599                 if (prependComma) {
11600                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11601                 } else {
11602                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11603                     prependComma = 1;
11604                 }
11605
11606                 strcat(string, number);
11607             }
11608         }
11609         strcat(string, "]");
11610
11611         if (nCmailMovesRegistered + nCmailResults == 0) {
11612             switch (nCmailGames) {
11613               case 1:
11614                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11615                 break;
11616
11617               case 2:
11618                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11619                 break;
11620
11621               default:
11622                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11623                          nCmailGames);
11624                 break;
11625             }
11626         } else {
11627             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11628               case 1:
11629                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11630                          string);
11631                 break;
11632
11633               case 0:
11634                 if (nCmailResults == nCmailGames) {
11635                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11636                 } else {
11637                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11638                 }
11639                 break;
11640
11641               default:
11642                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11643                          string);
11644             }
11645         }
11646     }
11647     return cmailMsg;
11648 #endif /* WIN32 */
11649 }
11650
11651 void
11652 ResetGameEvent()
11653 {
11654     if (gameMode == Training)
11655       SetTrainingModeOff();
11656
11657     Reset(TRUE, TRUE);
11658     cmailMsgLoaded = FALSE;
11659     if (appData.icsActive) {
11660       SendToICS(ics_prefix);
11661       SendToICS("refresh\n");
11662     }
11663 }
11664
11665 void
11666 ExitEvent(status)
11667      int status;
11668 {
11669     exiting++;
11670     if (exiting > 2) {
11671       /* Give up on clean exit */
11672       exit(status);
11673     }
11674     if (exiting > 1) {
11675       /* Keep trying for clean exit */
11676       return;
11677     }
11678
11679     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11680
11681     if (telnetISR != NULL) {
11682       RemoveInputSource(telnetISR);
11683     }
11684     if (icsPR != NoProc) {
11685       DestroyChildProcess(icsPR, TRUE);
11686     }
11687
11688     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11689     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11690
11691     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11692     /* make sure this other one finishes before killing it!                  */
11693     if(endingGame) { int count = 0;
11694         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11695         while(endingGame && count++ < 10) DoSleep(1);
11696         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11697     }
11698
11699     /* Kill off chess programs */
11700     if (first.pr != NoProc) {
11701         ExitAnalyzeMode();
11702
11703         DoSleep( appData.delayBeforeQuit );
11704         SendToProgram("quit\n", &first);
11705         DoSleep( appData.delayAfterQuit );
11706         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11707     }
11708     if (second.pr != NoProc) {
11709         DoSleep( appData.delayBeforeQuit );
11710         SendToProgram("quit\n", &second);
11711         DoSleep( appData.delayAfterQuit );
11712         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11713     }
11714     if (first.isr != NULL) {
11715         RemoveInputSource(first.isr);
11716     }
11717     if (second.isr != NULL) {
11718         RemoveInputSource(second.isr);
11719     }
11720
11721     ShutDownFrontEnd();
11722     exit(status);
11723 }
11724
11725 void
11726 PauseEvent()
11727 {
11728     if (appData.debugMode)
11729         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11730     if (pausing) {
11731         pausing = FALSE;
11732         ModeHighlight();
11733         if (gameMode == MachinePlaysWhite ||
11734             gameMode == MachinePlaysBlack) {
11735             StartClocks();
11736         } else {
11737             DisplayBothClocks();
11738         }
11739         if (gameMode == PlayFromGameFile) {
11740             if (appData.timeDelay >= 0)
11741                 AutoPlayGameLoop();
11742         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11743             Reset(FALSE, TRUE);
11744             SendToICS(ics_prefix);
11745             SendToICS("refresh\n");
11746         } else if (currentMove < forwardMostMove) {
11747             ForwardInner(forwardMostMove);
11748         }
11749         pauseExamInvalid = FALSE;
11750     } else {
11751         switch (gameMode) {
11752           default:
11753             return;
11754           case IcsExamining:
11755             pauseExamForwardMostMove = forwardMostMove;
11756             pauseExamInvalid = FALSE;
11757             /* fall through */
11758           case IcsObserving:
11759           case IcsPlayingWhite:
11760           case IcsPlayingBlack:
11761             pausing = TRUE;
11762             ModeHighlight();
11763             return;
11764           case PlayFromGameFile:
11765             (void) StopLoadGameTimer();
11766             pausing = TRUE;
11767             ModeHighlight();
11768             break;
11769           case BeginningOfGame:
11770             if (appData.icsActive) return;
11771             /* else fall through */
11772           case MachinePlaysWhite:
11773           case MachinePlaysBlack:
11774           case TwoMachinesPlay:
11775             if (forwardMostMove == 0)
11776               return;           /* don't pause if no one has moved */
11777             if ((gameMode == MachinePlaysWhite &&
11778                  !WhiteOnMove(forwardMostMove)) ||
11779                 (gameMode == MachinePlaysBlack &&
11780                  WhiteOnMove(forwardMostMove))) {
11781                 StopClocks();
11782             }
11783             pausing = TRUE;
11784             ModeHighlight();
11785             break;
11786         }
11787     }
11788 }
11789
11790 void
11791 EditCommentEvent()
11792 {
11793     char title[MSG_SIZ];
11794
11795     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11796       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11797     } else {
11798       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11799                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11800                parseList[currentMove - 1]);
11801     }
11802
11803     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11804 }
11805
11806
11807 void
11808 EditTagsEvent()
11809 {
11810     char *tags = PGNTags(&gameInfo);
11811     EditTagsPopUp(tags, NULL);
11812     free(tags);
11813 }
11814
11815 void
11816 AnalyzeModeEvent()
11817 {
11818     if (appData.noChessProgram || gameMode == AnalyzeMode)
11819       return;
11820
11821     if (gameMode != AnalyzeFile) {
11822         if (!appData.icsEngineAnalyze) {
11823                EditGameEvent();
11824                if (gameMode != EditGame) return;
11825         }
11826         ResurrectChessProgram();
11827         SendToProgram("analyze\n", &first);
11828         first.analyzing = TRUE;
11829         /*first.maybeThinking = TRUE;*/
11830         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11831         EngineOutputPopUp();
11832     }
11833     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11834     pausing = FALSE;
11835     ModeHighlight();
11836     SetGameInfo();
11837
11838     StartAnalysisClock();
11839     GetTimeMark(&lastNodeCountTime);
11840     lastNodeCount = 0;
11841 }
11842
11843 void
11844 AnalyzeFileEvent()
11845 {
11846     if (appData.noChessProgram || gameMode == AnalyzeFile)
11847       return;
11848
11849     if (gameMode != AnalyzeMode) {
11850         EditGameEvent();
11851         if (gameMode != EditGame) return;
11852         ResurrectChessProgram();
11853         SendToProgram("analyze\n", &first);
11854         first.analyzing = TRUE;
11855         /*first.maybeThinking = TRUE;*/
11856         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11857         EngineOutputPopUp();
11858     }
11859     gameMode = AnalyzeFile;
11860     pausing = FALSE;
11861     ModeHighlight();
11862     SetGameInfo();
11863
11864     StartAnalysisClock();
11865     GetTimeMark(&lastNodeCountTime);
11866     lastNodeCount = 0;
11867 }
11868
11869 void
11870 MachineWhiteEvent()
11871 {
11872     char buf[MSG_SIZ];
11873     char *bookHit = NULL;
11874
11875     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11876       return;
11877
11878
11879     if (gameMode == PlayFromGameFile ||
11880         gameMode == TwoMachinesPlay  ||
11881         gameMode == Training         ||
11882         gameMode == AnalyzeMode      ||
11883         gameMode == EndOfGame)
11884         EditGameEvent();
11885
11886     if (gameMode == EditPosition)
11887         EditPositionDone(TRUE);
11888
11889     if (!WhiteOnMove(currentMove)) {
11890         DisplayError(_("It is not White's turn"), 0);
11891         return;
11892     }
11893
11894     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11895       ExitAnalyzeMode();
11896
11897     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11898         gameMode == AnalyzeFile)
11899         TruncateGame();
11900
11901     ResurrectChessProgram();    /* in case it isn't running */
11902     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11903         gameMode = MachinePlaysWhite;
11904         ResetClocks();
11905     } else
11906     gameMode = MachinePlaysWhite;
11907     pausing = FALSE;
11908     ModeHighlight();
11909     SetGameInfo();
11910     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11911     DisplayTitle(buf);
11912     if (first.sendName) {
11913       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11914       SendToProgram(buf, &first);
11915     }
11916     if (first.sendTime) {
11917       if (first.useColors) {
11918         SendToProgram("black\n", &first); /*gnu kludge*/
11919       }
11920       SendTimeRemaining(&first, TRUE);
11921     }
11922     if (first.useColors) {
11923       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11924     }
11925     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11926     SetMachineThinkingEnables();
11927     first.maybeThinking = TRUE;
11928     StartClocks();
11929     firstMove = FALSE;
11930
11931     if (appData.autoFlipView && !flipView) {
11932       flipView = !flipView;
11933       DrawPosition(FALSE, NULL);
11934       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11935     }
11936
11937     if(bookHit) { // [HGM] book: simulate book reply
11938         static char bookMove[MSG_SIZ]; // a bit generous?
11939
11940         programStats.nodes = programStats.depth = programStats.time =
11941         programStats.score = programStats.got_only_move = 0;
11942         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11943
11944         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11945         strcat(bookMove, bookHit);
11946         HandleMachineMove(bookMove, &first);
11947     }
11948 }
11949
11950 void
11951 MachineBlackEvent()
11952 {
11953   char buf[MSG_SIZ];
11954   char *bookHit = NULL;
11955
11956     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11957         return;
11958
11959
11960     if (gameMode == PlayFromGameFile ||
11961         gameMode == TwoMachinesPlay  ||
11962         gameMode == Training         ||
11963         gameMode == AnalyzeMode      ||
11964         gameMode == EndOfGame)
11965         EditGameEvent();
11966
11967     if (gameMode == EditPosition)
11968         EditPositionDone(TRUE);
11969
11970     if (WhiteOnMove(currentMove)) {
11971         DisplayError(_("It is not Black's turn"), 0);
11972         return;
11973     }
11974
11975     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11976       ExitAnalyzeMode();
11977
11978     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11979         gameMode == AnalyzeFile)
11980         TruncateGame();
11981
11982     ResurrectChessProgram();    /* in case it isn't running */
11983     gameMode = MachinePlaysBlack;
11984     pausing = FALSE;
11985     ModeHighlight();
11986     SetGameInfo();
11987     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11988     DisplayTitle(buf);
11989     if (first.sendName) {
11990       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11991       SendToProgram(buf, &first);
11992     }
11993     if (first.sendTime) {
11994       if (first.useColors) {
11995         SendToProgram("white\n", &first); /*gnu kludge*/
11996       }
11997       SendTimeRemaining(&first, FALSE);
11998     }
11999     if (first.useColors) {
12000       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12001     }
12002     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12003     SetMachineThinkingEnables();
12004     first.maybeThinking = TRUE;
12005     StartClocks();
12006
12007     if (appData.autoFlipView && flipView) {
12008       flipView = !flipView;
12009       DrawPosition(FALSE, NULL);
12010       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12011     }
12012     if(bookHit) { // [HGM] book: simulate book reply
12013         static char bookMove[MSG_SIZ]; // a bit generous?
12014
12015         programStats.nodes = programStats.depth = programStats.time =
12016         programStats.score = programStats.got_only_move = 0;
12017         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12018
12019         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12020         strcat(bookMove, bookHit);
12021         HandleMachineMove(bookMove, &first);
12022     }
12023 }
12024
12025
12026 void
12027 DisplayTwoMachinesTitle()
12028 {
12029     char buf[MSG_SIZ];
12030     if (appData.matchGames > 0) {
12031         if (first.twoMachinesColor[0] == 'w') {
12032           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12033                    gameInfo.white, gameInfo.black,
12034                    first.matchWins, second.matchWins,
12035                    matchGame - 1 - (first.matchWins + second.matchWins));
12036         } else {
12037           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12038                    gameInfo.white, gameInfo.black,
12039                    second.matchWins, first.matchWins,
12040                    matchGame - 1 - (first.matchWins + second.matchWins));
12041         }
12042     } else {
12043       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12044     }
12045     DisplayTitle(buf);
12046 }
12047
12048 void
12049 SettingsMenuIfReady()
12050 {
12051   if (second.lastPing != second.lastPong) {
12052     DisplayMessage("", _("Waiting for second chess program"));
12053     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12054     return;
12055   }
12056   ThawUI();
12057   DisplayMessage("", "");
12058   SettingsPopUp(&second);
12059 }
12060
12061 int
12062 WaitForSecond(DelayedEventCallback retry)
12063 {
12064     if (second.pr == NULL) {
12065         StartChessProgram(&second);
12066         if (second.protocolVersion == 1) {
12067           retry();
12068         } else {
12069           /* kludge: allow timeout for initial "feature" command */
12070           FreezeUI();
12071           DisplayMessage("", _("Starting second chess program"));
12072           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12073         }
12074         return 1;
12075     }
12076     return 0;
12077 }
12078
12079 void
12080 TwoMachinesEvent P((void))
12081 {
12082     int i;
12083     char buf[MSG_SIZ];
12084     ChessProgramState *onmove;
12085     char *bookHit = NULL;
12086     static int stalling = 0;
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     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12124       DisplayMessage("", _("Waiting for first chess program"));
12125       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12126       return;
12127     }
12128     if(!stalling) {
12129       InitChessProgram(&second, FALSE);
12130       SendToProgram("force\n", &second);
12131     }
12132     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12133       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12134       stalling = 1;
12135       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12136       return;
12137     }
12138     stalling = 0;
12139     DisplayMessage("", "");
12140     if (startedFromSetupPosition) {
12141         SendBoard(&second, backwardMostMove);
12142     if (appData.debugMode) {
12143         fprintf(debugFP, "Two Machines\n");
12144     }
12145     }
12146     for (i = backwardMostMove; i < forwardMostMove; i++) {
12147         SendMoveToProgram(i, &second);
12148     }
12149
12150     gameMode = TwoMachinesPlay;
12151     pausing = FALSE;
12152     ModeHighlight();
12153     SetGameInfo();
12154     DisplayTwoMachinesTitle();
12155     firstMove = TRUE;
12156     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12157         onmove = &first;
12158     } else {
12159         onmove = &second;
12160     }
12161
12162     SendToProgram(first.computerString, &first);
12163     if (first.sendName) {
12164       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12165       SendToProgram(buf, &first);
12166     }
12167     SendToProgram(second.computerString, &second);
12168     if (second.sendName) {
12169       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12170       SendToProgram(buf, &second);
12171     }
12172
12173     ResetClocks();
12174     if (!first.sendTime || !second.sendTime) {
12175         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12176         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12177     }
12178     if (onmove->sendTime) {
12179       if (onmove->useColors) {
12180         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12181       }
12182       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12183     }
12184     if (onmove->useColors) {
12185       SendToProgram(onmove->twoMachinesColor, onmove);
12186     }
12187     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12188 //    SendToProgram("go\n", onmove);
12189     onmove->maybeThinking = TRUE;
12190     SetMachineThinkingEnables();
12191
12192     StartClocks();
12193
12194     if(bookHit) { // [HGM] book: simulate book reply
12195         static char bookMove[MSG_SIZ]; // a bit generous?
12196
12197         programStats.nodes = programStats.depth = programStats.time =
12198         programStats.score = programStats.got_only_move = 0;
12199         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12200
12201         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12202         strcat(bookMove, bookHit);
12203         savedMessage = bookMove; // args for deferred call
12204         savedState = onmove;
12205         ScheduleDelayedEvent(DeferredBookMove, 1);
12206     }
12207 }
12208
12209 void
12210 TrainingEvent()
12211 {
12212     if (gameMode == Training) {
12213       SetTrainingModeOff();
12214       gameMode = PlayFromGameFile;
12215       DisplayMessage("", _("Training mode off"));
12216     } else {
12217       gameMode = Training;
12218       animateTraining = appData.animate;
12219
12220       /* make sure we are not already at the end of the game */
12221       if (currentMove < forwardMostMove) {
12222         SetTrainingModeOn();
12223         DisplayMessage("", _("Training mode on"));
12224       } else {
12225         gameMode = PlayFromGameFile;
12226         DisplayError(_("Already at end of game"), 0);
12227       }
12228     }
12229     ModeHighlight();
12230 }
12231
12232 void
12233 IcsClientEvent()
12234 {
12235     if (!appData.icsActive) return;
12236     switch (gameMode) {
12237       case IcsPlayingWhite:
12238       case IcsPlayingBlack:
12239       case IcsObserving:
12240       case IcsIdle:
12241       case BeginningOfGame:
12242       case IcsExamining:
12243         return;
12244
12245       case EditGame:
12246         break;
12247
12248       case EditPosition:
12249         EditPositionDone(TRUE);
12250         break;
12251
12252       case AnalyzeMode:
12253       case AnalyzeFile:
12254         ExitAnalyzeMode();
12255         break;
12256
12257       default:
12258         EditGameEvent();
12259         break;
12260     }
12261
12262     gameMode = IcsIdle;
12263     ModeHighlight();
12264     return;
12265 }
12266
12267
12268 void
12269 EditGameEvent()
12270 {
12271     int i;
12272
12273     switch (gameMode) {
12274       case Training:
12275         SetTrainingModeOff();
12276         break;
12277       case MachinePlaysWhite:
12278       case MachinePlaysBlack:
12279       case BeginningOfGame:
12280         SendToProgram("force\n", &first);
12281         SetUserThinkingEnables();
12282         break;
12283       case PlayFromGameFile:
12284         (void) StopLoadGameTimer();
12285         if (gameFileFP != NULL) {
12286             gameFileFP = NULL;
12287         }
12288         break;
12289       case EditPosition:
12290         EditPositionDone(TRUE);
12291         break;
12292       case AnalyzeMode:
12293       case AnalyzeFile:
12294         ExitAnalyzeMode();
12295         SendToProgram("force\n", &first);
12296         break;
12297       case TwoMachinesPlay:
12298         GameEnds(EndOfFile, NULL, GE_PLAYER);
12299         ResurrectChessProgram();
12300         SetUserThinkingEnables();
12301         break;
12302       case EndOfGame:
12303         ResurrectChessProgram();
12304         break;
12305       case IcsPlayingBlack:
12306       case IcsPlayingWhite:
12307         DisplayError(_("Warning: You are still playing a game"), 0);
12308         break;
12309       case IcsObserving:
12310         DisplayError(_("Warning: You are still observing a game"), 0);
12311         break;
12312       case IcsExamining:
12313         DisplayError(_("Warning: You are still examining a game"), 0);
12314         break;
12315       case IcsIdle:
12316         break;
12317       case EditGame:
12318       default:
12319         return;
12320     }
12321
12322     pausing = FALSE;
12323     StopClocks();
12324     first.offeredDraw = second.offeredDraw = 0;
12325
12326     if (gameMode == PlayFromGameFile) {
12327         whiteTimeRemaining = timeRemaining[0][currentMove];
12328         blackTimeRemaining = timeRemaining[1][currentMove];
12329         DisplayTitle("");
12330     }
12331
12332     if (gameMode == MachinePlaysWhite ||
12333         gameMode == MachinePlaysBlack ||
12334         gameMode == TwoMachinesPlay ||
12335         gameMode == EndOfGame) {
12336         i = forwardMostMove;
12337         while (i > currentMove) {
12338             SendToProgram("undo\n", &first);
12339             i--;
12340         }
12341         whiteTimeRemaining = timeRemaining[0][currentMove];
12342         blackTimeRemaining = timeRemaining[1][currentMove];
12343         DisplayBothClocks();
12344         if (whiteFlag || blackFlag) {
12345             whiteFlag = blackFlag = 0;
12346         }
12347         DisplayTitle("");
12348     }
12349
12350     gameMode = EditGame;
12351     ModeHighlight();
12352     SetGameInfo();
12353 }
12354
12355
12356 void
12357 EditPositionEvent()
12358 {
12359     if (gameMode == EditPosition) {
12360         EditGameEvent();
12361         return;
12362     }
12363
12364     EditGameEvent();
12365     if (gameMode != EditGame) return;
12366
12367     gameMode = EditPosition;
12368     ModeHighlight();
12369     SetGameInfo();
12370     if (currentMove > 0)
12371       CopyBoard(boards[0], boards[currentMove]);
12372
12373     blackPlaysFirst = !WhiteOnMove(currentMove);
12374     ResetClocks();
12375     currentMove = forwardMostMove = backwardMostMove = 0;
12376     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12377     DisplayMove(-1);
12378 }
12379
12380 void
12381 ExitAnalyzeMode()
12382 {
12383     /* [DM] icsEngineAnalyze - possible call from other functions */
12384     if (appData.icsEngineAnalyze) {
12385         appData.icsEngineAnalyze = FALSE;
12386
12387         DisplayMessage("",_("Close ICS engine analyze..."));
12388     }
12389     if (first.analysisSupport && first.analyzing) {
12390       SendToProgram("exit\n", &first);
12391       first.analyzing = FALSE;
12392     }
12393     thinkOutput[0] = NULLCHAR;
12394 }
12395
12396 void
12397 EditPositionDone(Boolean fakeRights)
12398 {
12399     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12400
12401     startedFromSetupPosition = TRUE;
12402     InitChessProgram(&first, FALSE);
12403     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12404       boards[0][EP_STATUS] = EP_NONE;
12405       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12406     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12407         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12408         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12409       } else boards[0][CASTLING][2] = NoRights;
12410     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12411         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12412         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12413       } else boards[0][CASTLING][5] = NoRights;
12414     }
12415     SendToProgram("force\n", &first);
12416     if (blackPlaysFirst) {
12417         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12418         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12419         currentMove = forwardMostMove = backwardMostMove = 1;
12420         CopyBoard(boards[1], boards[0]);
12421     } else {
12422         currentMove = forwardMostMove = backwardMostMove = 0;
12423     }
12424     SendBoard(&first, forwardMostMove);
12425     if (appData.debugMode) {
12426         fprintf(debugFP, "EditPosDone\n");
12427     }
12428     DisplayTitle("");
12429     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12430     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12431     gameMode = EditGame;
12432     ModeHighlight();
12433     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12434     ClearHighlights(); /* [AS] */
12435 }
12436
12437 /* Pause for `ms' milliseconds */
12438 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12439 void
12440 TimeDelay(ms)
12441      long ms;
12442 {
12443     TimeMark m1, m2;
12444
12445     GetTimeMark(&m1);
12446     do {
12447         GetTimeMark(&m2);
12448     } while (SubtractTimeMarks(&m2, &m1) < ms);
12449 }
12450
12451 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12452 void
12453 SendMultiLineToICS(buf)
12454      char *buf;
12455 {
12456     char temp[MSG_SIZ+1], *p;
12457     int len;
12458
12459     len = strlen(buf);
12460     if (len > MSG_SIZ)
12461       len = MSG_SIZ;
12462
12463     strncpy(temp, buf, len);
12464     temp[len] = 0;
12465
12466     p = temp;
12467     while (*p) {
12468         if (*p == '\n' || *p == '\r')
12469           *p = ' ';
12470         ++p;
12471     }
12472
12473     strcat(temp, "\n");
12474     SendToICS(temp);
12475     SendToPlayer(temp, strlen(temp));
12476 }
12477
12478 void
12479 SetWhiteToPlayEvent()
12480 {
12481     if (gameMode == EditPosition) {
12482         blackPlaysFirst = FALSE;
12483         DisplayBothClocks();    /* works because currentMove is 0 */
12484     } else if (gameMode == IcsExamining) {
12485         SendToICS(ics_prefix);
12486         SendToICS("tomove white\n");
12487     }
12488 }
12489
12490 void
12491 SetBlackToPlayEvent()
12492 {
12493     if (gameMode == EditPosition) {
12494         blackPlaysFirst = TRUE;
12495         currentMove = 1;        /* kludge */
12496         DisplayBothClocks();
12497         currentMove = 0;
12498     } else if (gameMode == IcsExamining) {
12499         SendToICS(ics_prefix);
12500         SendToICS("tomove black\n");
12501     }
12502 }
12503
12504 void
12505 EditPositionMenuEvent(selection, x, y)
12506      ChessSquare selection;
12507      int x, y;
12508 {
12509     char buf[MSG_SIZ];
12510     ChessSquare piece = boards[0][y][x];
12511
12512     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12513
12514     switch (selection) {
12515       case ClearBoard:
12516         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12517             SendToICS(ics_prefix);
12518             SendToICS("bsetup clear\n");
12519         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12520             SendToICS(ics_prefix);
12521             SendToICS("clearboard\n");
12522         } else {
12523             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12524                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12525                 for (y = 0; y < BOARD_HEIGHT; y++) {
12526                     if (gameMode == IcsExamining) {
12527                         if (boards[currentMove][y][x] != EmptySquare) {
12528                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12529                                     AAA + x, ONE + y);
12530                             SendToICS(buf);
12531                         }
12532                     } else {
12533                         boards[0][y][x] = p;
12534                     }
12535                 }
12536             }
12537         }
12538         if (gameMode == EditPosition) {
12539             DrawPosition(FALSE, boards[0]);
12540         }
12541         break;
12542
12543       case WhitePlay:
12544         SetWhiteToPlayEvent();
12545         break;
12546
12547       case BlackPlay:
12548         SetBlackToPlayEvent();
12549         break;
12550
12551       case EmptySquare:
12552         if (gameMode == IcsExamining) {
12553             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12554             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12555             SendToICS(buf);
12556         } else {
12557             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12558                 if(x == BOARD_LEFT-2) {
12559                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12560                     boards[0][y][1] = 0;
12561                 } else
12562                 if(x == BOARD_RGHT+1) {
12563                     if(y >= gameInfo.holdingsSize) break;
12564                     boards[0][y][BOARD_WIDTH-2] = 0;
12565                 } else break;
12566             }
12567             boards[0][y][x] = EmptySquare;
12568             DrawPosition(FALSE, boards[0]);
12569         }
12570         break;
12571
12572       case PromotePiece:
12573         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12574            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12575             selection = (ChessSquare) (PROMOTED piece);
12576         } else if(piece == EmptySquare) selection = WhiteSilver;
12577         else selection = (ChessSquare)((int)piece - 1);
12578         goto defaultlabel;
12579
12580       case DemotePiece:
12581         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12582            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12583             selection = (ChessSquare) (DEMOTED piece);
12584         } else if(piece == EmptySquare) selection = BlackSilver;
12585         else selection = (ChessSquare)((int)piece + 1);
12586         goto defaultlabel;
12587
12588       case WhiteQueen:
12589       case BlackQueen:
12590         if(gameInfo.variant == VariantShatranj ||
12591            gameInfo.variant == VariantXiangqi  ||
12592            gameInfo.variant == VariantCourier  ||
12593            gameInfo.variant == VariantMakruk     )
12594             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12595         goto defaultlabel;
12596
12597       case WhiteKing:
12598       case BlackKing:
12599         if(gameInfo.variant == VariantXiangqi)
12600             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12601         if(gameInfo.variant == VariantKnightmate)
12602             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12603       default:
12604         defaultlabel:
12605         if (gameMode == IcsExamining) {
12606             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12607             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12608                      PieceToChar(selection), AAA + x, ONE + y);
12609             SendToICS(buf);
12610         } else {
12611             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12612                 int n;
12613                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12614                     n = PieceToNumber(selection - BlackPawn);
12615                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12616                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12617                     boards[0][BOARD_HEIGHT-1-n][1]++;
12618                 } else
12619                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12620                     n = PieceToNumber(selection);
12621                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12622                     boards[0][n][BOARD_WIDTH-1] = selection;
12623                     boards[0][n][BOARD_WIDTH-2]++;
12624                 }
12625             } else
12626             boards[0][y][x] = selection;
12627             DrawPosition(TRUE, boards[0]);
12628         }
12629         break;
12630     }
12631 }
12632
12633
12634 void
12635 DropMenuEvent(selection, x, y)
12636      ChessSquare selection;
12637      int x, y;
12638 {
12639     ChessMove moveType;
12640
12641     switch (gameMode) {
12642       case IcsPlayingWhite:
12643       case MachinePlaysBlack:
12644         if (!WhiteOnMove(currentMove)) {
12645             DisplayMoveError(_("It is Black's turn"));
12646             return;
12647         }
12648         moveType = WhiteDrop;
12649         break;
12650       case IcsPlayingBlack:
12651       case MachinePlaysWhite:
12652         if (WhiteOnMove(currentMove)) {
12653             DisplayMoveError(_("It is White's turn"));
12654             return;
12655         }
12656         moveType = BlackDrop;
12657         break;
12658       case EditGame:
12659         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12660         break;
12661       default:
12662         return;
12663     }
12664
12665     if (moveType == BlackDrop && selection < BlackPawn) {
12666       selection = (ChessSquare) ((int) selection
12667                                  + (int) BlackPawn - (int) WhitePawn);
12668     }
12669     if (boards[currentMove][y][x] != EmptySquare) {
12670         DisplayMoveError(_("That square is occupied"));
12671         return;
12672     }
12673
12674     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12675 }
12676
12677 void
12678 AcceptEvent()
12679 {
12680     /* Accept a pending offer of any kind from opponent */
12681
12682     if (appData.icsActive) {
12683         SendToICS(ics_prefix);
12684         SendToICS("accept\n");
12685     } else if (cmailMsgLoaded) {
12686         if (currentMove == cmailOldMove &&
12687             commentList[cmailOldMove] != NULL &&
12688             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12689                    "Black offers a draw" : "White offers a draw")) {
12690             TruncateGame();
12691             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12692             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12693         } else {
12694             DisplayError(_("There is no pending offer on this move"), 0);
12695             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12696         }
12697     } else {
12698         /* Not used for offers from chess program */
12699     }
12700 }
12701
12702 void
12703 DeclineEvent()
12704 {
12705     /* Decline a pending offer of any kind from opponent */
12706
12707     if (appData.icsActive) {
12708         SendToICS(ics_prefix);
12709         SendToICS("decline\n");
12710     } else if (cmailMsgLoaded) {
12711         if (currentMove == cmailOldMove &&
12712             commentList[cmailOldMove] != NULL &&
12713             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12714                    "Black offers a draw" : "White offers a draw")) {
12715 #ifdef NOTDEF
12716             AppendComment(cmailOldMove, "Draw declined", TRUE);
12717             DisplayComment(cmailOldMove - 1, "Draw declined");
12718 #endif /*NOTDEF*/
12719         } else {
12720             DisplayError(_("There is no pending offer on this move"), 0);
12721         }
12722     } else {
12723         /* Not used for offers from chess program */
12724     }
12725 }
12726
12727 void
12728 RematchEvent()
12729 {
12730     /* Issue ICS rematch command */
12731     if (appData.icsActive) {
12732         SendToICS(ics_prefix);
12733         SendToICS("rematch\n");
12734     }
12735 }
12736
12737 void
12738 CallFlagEvent()
12739 {
12740     /* Call your opponent's flag (claim a win on time) */
12741     if (appData.icsActive) {
12742         SendToICS(ics_prefix);
12743         SendToICS("flag\n");
12744     } else {
12745         switch (gameMode) {
12746           default:
12747             return;
12748           case MachinePlaysWhite:
12749             if (whiteFlag) {
12750                 if (blackFlag)
12751                   GameEnds(GameIsDrawn, "Both players ran out of time",
12752                            GE_PLAYER);
12753                 else
12754                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12755             } else {
12756                 DisplayError(_("Your opponent is not out of time"), 0);
12757             }
12758             break;
12759           case MachinePlaysBlack:
12760             if (blackFlag) {
12761                 if (whiteFlag)
12762                   GameEnds(GameIsDrawn, "Both players ran out of time",
12763                            GE_PLAYER);
12764                 else
12765                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12766             } else {
12767                 DisplayError(_("Your opponent is not out of time"), 0);
12768             }
12769             break;
12770         }
12771     }
12772 }
12773
12774 void
12775 ClockClick(int which)
12776 {       // [HGM] code moved to back-end from winboard.c
12777         if(which) { // black clock
12778           if (gameMode == EditPosition || gameMode == IcsExamining) {
12779             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12780             SetBlackToPlayEvent();
12781           } else if (gameMode == EditGame || shiftKey) {
12782             AdjustClock(which, -1);
12783           } else if (gameMode == IcsPlayingWhite ||
12784                      gameMode == MachinePlaysBlack) {
12785             CallFlagEvent();
12786           }
12787         } else { // white clock
12788           if (gameMode == EditPosition || gameMode == IcsExamining) {
12789             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12790             SetWhiteToPlayEvent();
12791           } else if (gameMode == EditGame || shiftKey) {
12792             AdjustClock(which, -1);
12793           } else if (gameMode == IcsPlayingBlack ||
12794                    gameMode == MachinePlaysWhite) {
12795             CallFlagEvent();
12796           }
12797         }
12798 }
12799
12800 void
12801 DrawEvent()
12802 {
12803     /* Offer draw or accept pending draw offer from opponent */
12804
12805     if (appData.icsActive) {
12806         /* Note: tournament rules require draw offers to be
12807            made after you make your move but before you punch
12808            your clock.  Currently ICS doesn't let you do that;
12809            instead, you immediately punch your clock after making
12810            a move, but you can offer a draw at any time. */
12811
12812         SendToICS(ics_prefix);
12813         SendToICS("draw\n");
12814         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12815     } else if (cmailMsgLoaded) {
12816         if (currentMove == cmailOldMove &&
12817             commentList[cmailOldMove] != NULL &&
12818             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12819                    "Black offers a draw" : "White offers a draw")) {
12820             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12821             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12822         } else if (currentMove == cmailOldMove + 1) {
12823             char *offer = WhiteOnMove(cmailOldMove) ?
12824               "White offers a draw" : "Black offers a draw";
12825             AppendComment(currentMove, offer, TRUE);
12826             DisplayComment(currentMove - 1, offer);
12827             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12828         } else {
12829             DisplayError(_("You must make your move before offering a draw"), 0);
12830             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12831         }
12832     } else if (first.offeredDraw) {
12833         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12834     } else {
12835         if (first.sendDrawOffers) {
12836             SendToProgram("draw\n", &first);
12837             userOfferedDraw = TRUE;
12838         }
12839     }
12840 }
12841
12842 void
12843 AdjournEvent()
12844 {
12845     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12846
12847     if (appData.icsActive) {
12848         SendToICS(ics_prefix);
12849         SendToICS("adjourn\n");
12850     } else {
12851         /* Currently GNU Chess doesn't offer or accept Adjourns */
12852     }
12853 }
12854
12855
12856 void
12857 AbortEvent()
12858 {
12859     /* Offer Abort or accept pending Abort offer from opponent */
12860
12861     if (appData.icsActive) {
12862         SendToICS(ics_prefix);
12863         SendToICS("abort\n");
12864     } else {
12865         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12866     }
12867 }
12868
12869 void
12870 ResignEvent()
12871 {
12872     /* Resign.  You can do this even if it's not your turn. */
12873
12874     if (appData.icsActive) {
12875         SendToICS(ics_prefix);
12876         SendToICS("resign\n");
12877     } else {
12878         switch (gameMode) {
12879           case MachinePlaysWhite:
12880             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12881             break;
12882           case MachinePlaysBlack:
12883             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12884             break;
12885           case EditGame:
12886             if (cmailMsgLoaded) {
12887                 TruncateGame();
12888                 if (WhiteOnMove(cmailOldMove)) {
12889                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12890                 } else {
12891                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12892                 }
12893                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12894             }
12895             break;
12896           default:
12897             break;
12898         }
12899     }
12900 }
12901
12902
12903 void
12904 StopObservingEvent()
12905 {
12906     /* Stop observing current games */
12907     SendToICS(ics_prefix);
12908     SendToICS("unobserve\n");
12909 }
12910
12911 void
12912 StopExaminingEvent()
12913 {
12914     /* Stop observing current game */
12915     SendToICS(ics_prefix);
12916     SendToICS("unexamine\n");
12917 }
12918
12919 void
12920 ForwardInner(target)
12921      int target;
12922 {
12923     int limit;
12924
12925     if (appData.debugMode)
12926         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12927                 target, currentMove, forwardMostMove);
12928
12929     if (gameMode == EditPosition)
12930       return;
12931
12932     if (gameMode == PlayFromGameFile && !pausing)
12933       PauseEvent();
12934
12935     if (gameMode == IcsExamining && pausing)
12936       limit = pauseExamForwardMostMove;
12937     else
12938       limit = forwardMostMove;
12939
12940     if (target > limit) target = limit;
12941
12942     if (target > 0 && moveList[target - 1][0]) {
12943         int fromX, fromY, toX, toY;
12944         toX = moveList[target - 1][2] - AAA;
12945         toY = moveList[target - 1][3] - ONE;
12946         if (moveList[target - 1][1] == '@') {
12947             if (appData.highlightLastMove) {
12948                 SetHighlights(-1, -1, toX, toY);
12949             }
12950         } else {
12951             fromX = moveList[target - 1][0] - AAA;
12952             fromY = moveList[target - 1][1] - ONE;
12953             if (target == currentMove + 1) {
12954                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12955             }
12956             if (appData.highlightLastMove) {
12957                 SetHighlights(fromX, fromY, toX, toY);
12958             }
12959         }
12960     }
12961     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12962         gameMode == Training || gameMode == PlayFromGameFile ||
12963         gameMode == AnalyzeFile) {
12964         while (currentMove < target) {
12965             SendMoveToProgram(currentMove++, &first);
12966         }
12967     } else {
12968         currentMove = target;
12969     }
12970
12971     if (gameMode == EditGame || gameMode == EndOfGame) {
12972         whiteTimeRemaining = timeRemaining[0][currentMove];
12973         blackTimeRemaining = timeRemaining[1][currentMove];
12974     }
12975     DisplayBothClocks();
12976     DisplayMove(currentMove - 1);
12977     DrawPosition(FALSE, boards[currentMove]);
12978     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12979     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12980         DisplayComment(currentMove - 1, commentList[currentMove]);
12981     }
12982 }
12983
12984
12985 void
12986 ForwardEvent()
12987 {
12988     if (gameMode == IcsExamining && !pausing) {
12989         SendToICS(ics_prefix);
12990         SendToICS("forward\n");
12991     } else {
12992         ForwardInner(currentMove + 1);
12993     }
12994 }
12995
12996 void
12997 ToEndEvent()
12998 {
12999     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13000         /* to optimze, we temporarily turn off analysis mode while we feed
13001          * the remaining moves to the engine. Otherwise we get analysis output
13002          * after each move.
13003          */
13004         if (first.analysisSupport) {
13005           SendToProgram("exit\nforce\n", &first);
13006           first.analyzing = FALSE;
13007         }
13008     }
13009
13010     if (gameMode == IcsExamining && !pausing) {
13011         SendToICS(ics_prefix);
13012         SendToICS("forward 999999\n");
13013     } else {
13014         ForwardInner(forwardMostMove);
13015     }
13016
13017     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13018         /* we have fed all the moves, so reactivate analysis mode */
13019         SendToProgram("analyze\n", &first);
13020         first.analyzing = TRUE;
13021         /*first.maybeThinking = TRUE;*/
13022         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13023     }
13024 }
13025
13026 void
13027 BackwardInner(target)
13028      int target;
13029 {
13030     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13031
13032     if (appData.debugMode)
13033         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13034                 target, currentMove, forwardMostMove);
13035
13036     if (gameMode == EditPosition) return;
13037     if (currentMove <= backwardMostMove) {
13038         ClearHighlights();
13039         DrawPosition(full_redraw, boards[currentMove]);
13040         return;
13041     }
13042     if (gameMode == PlayFromGameFile && !pausing)
13043       PauseEvent();
13044
13045     if (moveList[target][0]) {
13046         int fromX, fromY, toX, toY;
13047         toX = moveList[target][2] - AAA;
13048         toY = moveList[target][3] - ONE;
13049         if (moveList[target][1] == '@') {
13050             if (appData.highlightLastMove) {
13051                 SetHighlights(-1, -1, toX, toY);
13052             }
13053         } else {
13054             fromX = moveList[target][0] - AAA;
13055             fromY = moveList[target][1] - ONE;
13056             if (target == currentMove - 1) {
13057                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13058             }
13059             if (appData.highlightLastMove) {
13060                 SetHighlights(fromX, fromY, toX, toY);
13061             }
13062         }
13063     }
13064     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13065         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13066         while (currentMove > target) {
13067             SendToProgram("undo\n", &first);
13068             currentMove--;
13069         }
13070     } else {
13071         currentMove = target;
13072     }
13073
13074     if (gameMode == EditGame || gameMode == EndOfGame) {
13075         whiteTimeRemaining = timeRemaining[0][currentMove];
13076         blackTimeRemaining = timeRemaining[1][currentMove];
13077     }
13078     DisplayBothClocks();
13079     DisplayMove(currentMove - 1);
13080     DrawPosition(full_redraw, boards[currentMove]);
13081     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13082     // [HGM] PV info: routine tests if comment empty
13083     DisplayComment(currentMove - 1, commentList[currentMove]);
13084 }
13085
13086 void
13087 BackwardEvent()
13088 {
13089     if (gameMode == IcsExamining && !pausing) {
13090         SendToICS(ics_prefix);
13091         SendToICS("backward\n");
13092     } else {
13093         BackwardInner(currentMove - 1);
13094     }
13095 }
13096
13097 void
13098 ToStartEvent()
13099 {
13100     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13101         /* to optimize, we temporarily turn off analysis mode while we undo
13102          * all the moves. Otherwise we get analysis output after each undo.
13103          */
13104         if (first.analysisSupport) {
13105           SendToProgram("exit\nforce\n", &first);
13106           first.analyzing = FALSE;
13107         }
13108     }
13109
13110     if (gameMode == IcsExamining && !pausing) {
13111         SendToICS(ics_prefix);
13112         SendToICS("backward 999999\n");
13113     } else {
13114         BackwardInner(backwardMostMove);
13115     }
13116
13117     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13118         /* we have fed all the moves, so reactivate analysis mode */
13119         SendToProgram("analyze\n", &first);
13120         first.analyzing = TRUE;
13121         /*first.maybeThinking = TRUE;*/
13122         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13123     }
13124 }
13125
13126 void
13127 ToNrEvent(int to)
13128 {
13129   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13130   if (to >= forwardMostMove) to = forwardMostMove;
13131   if (to <= backwardMostMove) to = backwardMostMove;
13132   if (to < currentMove) {
13133     BackwardInner(to);
13134   } else {
13135     ForwardInner(to);
13136   }
13137 }
13138
13139 void
13140 RevertEvent(Boolean annotate)
13141 {
13142     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13143         return;
13144     }
13145     if (gameMode != IcsExamining) {
13146         DisplayError(_("You are not examining a game"), 0);
13147         return;
13148     }
13149     if (pausing) {
13150         DisplayError(_("You can't revert while pausing"), 0);
13151         return;
13152     }
13153     SendToICS(ics_prefix);
13154     SendToICS("revert\n");
13155 }
13156
13157 void
13158 RetractMoveEvent()
13159 {
13160     switch (gameMode) {
13161       case MachinePlaysWhite:
13162       case MachinePlaysBlack:
13163         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13164             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13165             return;
13166         }
13167         if (forwardMostMove < 2) return;
13168         currentMove = forwardMostMove = forwardMostMove - 2;
13169         whiteTimeRemaining = timeRemaining[0][currentMove];
13170         blackTimeRemaining = timeRemaining[1][currentMove];
13171         DisplayBothClocks();
13172         DisplayMove(currentMove - 1);
13173         ClearHighlights();/*!! could figure this out*/
13174         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13175         SendToProgram("remove\n", &first);
13176         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13177         break;
13178
13179       case BeginningOfGame:
13180       default:
13181         break;
13182
13183       case IcsPlayingWhite:
13184       case IcsPlayingBlack:
13185         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13186             SendToICS(ics_prefix);
13187             SendToICS("takeback 2\n");
13188         } else {
13189             SendToICS(ics_prefix);
13190             SendToICS("takeback 1\n");
13191         }
13192         break;
13193     }
13194 }
13195
13196 void
13197 MoveNowEvent()
13198 {
13199     ChessProgramState *cps;
13200
13201     switch (gameMode) {
13202       case MachinePlaysWhite:
13203         if (!WhiteOnMove(forwardMostMove)) {
13204             DisplayError(_("It is your turn"), 0);
13205             return;
13206         }
13207         cps = &first;
13208         break;
13209       case MachinePlaysBlack:
13210         if (WhiteOnMove(forwardMostMove)) {
13211             DisplayError(_("It is your turn"), 0);
13212             return;
13213         }
13214         cps = &first;
13215         break;
13216       case TwoMachinesPlay:
13217         if (WhiteOnMove(forwardMostMove) ==
13218             (first.twoMachinesColor[0] == 'w')) {
13219             cps = &first;
13220         } else {
13221             cps = &second;
13222         }
13223         break;
13224       case BeginningOfGame:
13225       default:
13226         return;
13227     }
13228     SendToProgram("?\n", cps);
13229 }
13230
13231 void
13232 TruncateGameEvent()
13233 {
13234     EditGameEvent();
13235     if (gameMode != EditGame) return;
13236     TruncateGame();
13237 }
13238
13239 void
13240 TruncateGame()
13241 {
13242     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13243     if (forwardMostMove > currentMove) {
13244         if (gameInfo.resultDetails != NULL) {
13245             free(gameInfo.resultDetails);
13246             gameInfo.resultDetails = NULL;
13247             gameInfo.result = GameUnfinished;
13248         }
13249         forwardMostMove = currentMove;
13250         HistorySet(parseList, backwardMostMove, forwardMostMove,
13251                    currentMove-1);
13252     }
13253 }
13254
13255 void
13256 HintEvent()
13257 {
13258     if (appData.noChessProgram) return;
13259     switch (gameMode) {
13260       case MachinePlaysWhite:
13261         if (WhiteOnMove(forwardMostMove)) {
13262             DisplayError(_("Wait until your turn"), 0);
13263             return;
13264         }
13265         break;
13266       case BeginningOfGame:
13267       case MachinePlaysBlack:
13268         if (!WhiteOnMove(forwardMostMove)) {
13269             DisplayError(_("Wait until your turn"), 0);
13270             return;
13271         }
13272         break;
13273       default:
13274         DisplayError(_("No hint available"), 0);
13275         return;
13276     }
13277     SendToProgram("hint\n", &first);
13278     hintRequested = TRUE;
13279 }
13280
13281 void
13282 BookEvent()
13283 {
13284     if (appData.noChessProgram) return;
13285     switch (gameMode) {
13286       case MachinePlaysWhite:
13287         if (WhiteOnMove(forwardMostMove)) {
13288             DisplayError(_("Wait until your turn"), 0);
13289             return;
13290         }
13291         break;
13292       case BeginningOfGame:
13293       case MachinePlaysBlack:
13294         if (!WhiteOnMove(forwardMostMove)) {
13295             DisplayError(_("Wait until your turn"), 0);
13296             return;
13297         }
13298         break;
13299       case EditPosition:
13300         EditPositionDone(TRUE);
13301         break;
13302       case TwoMachinesPlay:
13303         return;
13304       default:
13305         break;
13306     }
13307     SendToProgram("bk\n", &first);
13308     bookOutput[0] = NULLCHAR;
13309     bookRequested = TRUE;
13310 }
13311
13312 void
13313 AboutGameEvent()
13314 {
13315     char *tags = PGNTags(&gameInfo);
13316     TagsPopUp(tags, CmailMsg());
13317     free(tags);
13318 }
13319
13320 /* end button procedures */
13321
13322 void
13323 PrintPosition(fp, move)
13324      FILE *fp;
13325      int move;
13326 {
13327     int i, j;
13328
13329     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13330         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13331             char c = PieceToChar(boards[move][i][j]);
13332             fputc(c == 'x' ? '.' : c, fp);
13333             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13334         }
13335     }
13336     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13337       fprintf(fp, "white to play\n");
13338     else
13339       fprintf(fp, "black to play\n");
13340 }
13341
13342 void
13343 PrintOpponents(fp)
13344      FILE *fp;
13345 {
13346     if (gameInfo.white != NULL) {
13347         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13348     } else {
13349         fprintf(fp, "\n");
13350     }
13351 }
13352
13353 /* Find last component of program's own name, using some heuristics */
13354 void
13355 TidyProgramName(prog, host, buf)
13356      char *prog, *host, buf[MSG_SIZ];
13357 {
13358     char *p, *q;
13359     int local = (strcmp(host, "localhost") == 0);
13360     while (!local && (p = strchr(prog, ';')) != NULL) {
13361         p++;
13362         while (*p == ' ') p++;
13363         prog = p;
13364     }
13365     if (*prog == '"' || *prog == '\'') {
13366         q = strchr(prog + 1, *prog);
13367     } else {
13368         q = strchr(prog, ' ');
13369     }
13370     if (q == NULL) q = prog + strlen(prog);
13371     p = q;
13372     while (p >= prog && *p != '/' && *p != '\\') p--;
13373     p++;
13374     if(p == prog && *p == '"') p++;
13375     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13376     memcpy(buf, p, q - p);
13377     buf[q - p] = NULLCHAR;
13378     if (!local) {
13379         strcat(buf, "@");
13380         strcat(buf, host);
13381     }
13382 }
13383
13384 char *
13385 TimeControlTagValue()
13386 {
13387     char buf[MSG_SIZ];
13388     if (!appData.clockMode) {
13389       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13390     } else if (movesPerSession > 0) {
13391       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13392     } else if (timeIncrement == 0) {
13393       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13394     } else {
13395       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13396     }
13397     return StrSave(buf);
13398 }
13399
13400 void
13401 SetGameInfo()
13402 {
13403     /* This routine is used only for certain modes */
13404     VariantClass v = gameInfo.variant;
13405     ChessMove r = GameUnfinished;
13406     char *p = NULL;
13407
13408     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13409         r = gameInfo.result;
13410         p = gameInfo.resultDetails;
13411         gameInfo.resultDetails = NULL;
13412     }
13413     ClearGameInfo(&gameInfo);
13414     gameInfo.variant = v;
13415
13416     switch (gameMode) {
13417       case MachinePlaysWhite:
13418         gameInfo.event = StrSave( appData.pgnEventHeader );
13419         gameInfo.site = StrSave(HostName());
13420         gameInfo.date = PGNDate();
13421         gameInfo.round = StrSave("-");
13422         gameInfo.white = StrSave(first.tidy);
13423         gameInfo.black = StrSave(UserName());
13424         gameInfo.timeControl = TimeControlTagValue();
13425         break;
13426
13427       case MachinePlaysBlack:
13428         gameInfo.event = StrSave( appData.pgnEventHeader );
13429         gameInfo.site = StrSave(HostName());
13430         gameInfo.date = PGNDate();
13431         gameInfo.round = StrSave("-");
13432         gameInfo.white = StrSave(UserName());
13433         gameInfo.black = StrSave(first.tidy);
13434         gameInfo.timeControl = TimeControlTagValue();
13435         break;
13436
13437       case TwoMachinesPlay:
13438         gameInfo.event = StrSave( appData.pgnEventHeader );
13439         gameInfo.site = StrSave(HostName());
13440         gameInfo.date = PGNDate();
13441         if (matchGame > 0) {
13442             char buf[MSG_SIZ];
13443             snprintf(buf, MSG_SIZ, "%d", matchGame);
13444             gameInfo.round = StrSave(buf);
13445         } else {
13446             gameInfo.round = StrSave("-");
13447         }
13448         if (first.twoMachinesColor[0] == 'w') {
13449             gameInfo.white = StrSave(first.tidy);
13450             gameInfo.black = StrSave(second.tidy);
13451         } else {
13452             gameInfo.white = StrSave(second.tidy);
13453             gameInfo.black = StrSave(first.tidy);
13454         }
13455         gameInfo.timeControl = TimeControlTagValue();
13456         break;
13457
13458       case EditGame:
13459         gameInfo.event = StrSave("Edited game");
13460         gameInfo.site = StrSave(HostName());
13461         gameInfo.date = PGNDate();
13462         gameInfo.round = StrSave("-");
13463         gameInfo.white = StrSave("-");
13464         gameInfo.black = StrSave("-");
13465         gameInfo.result = r;
13466         gameInfo.resultDetails = p;
13467         break;
13468
13469       case EditPosition:
13470         gameInfo.event = StrSave("Edited position");
13471         gameInfo.site = StrSave(HostName());
13472         gameInfo.date = PGNDate();
13473         gameInfo.round = StrSave("-");
13474         gameInfo.white = StrSave("-");
13475         gameInfo.black = StrSave("-");
13476         break;
13477
13478       case IcsPlayingWhite:
13479       case IcsPlayingBlack:
13480       case IcsObserving:
13481       case IcsExamining:
13482         break;
13483
13484       case PlayFromGameFile:
13485         gameInfo.event = StrSave("Game from non-PGN file");
13486         gameInfo.site = StrSave(HostName());
13487         gameInfo.date = PGNDate();
13488         gameInfo.round = StrSave("-");
13489         gameInfo.white = StrSave("?");
13490         gameInfo.black = StrSave("?");
13491         break;
13492
13493       default:
13494         break;
13495     }
13496 }
13497
13498 void
13499 ReplaceComment(index, text)
13500      int index;
13501      char *text;
13502 {
13503     int len;
13504     char *p;
13505     float score;
13506
13507     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13508        pvInfoList[index-1].depth == len &&
13509        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13510        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13511     while (*text == '\n') text++;
13512     len = strlen(text);
13513     while (len > 0 && text[len - 1] == '\n') len--;
13514
13515     if (commentList[index] != NULL)
13516       free(commentList[index]);
13517
13518     if (len == 0) {
13519         commentList[index] = NULL;
13520         return;
13521     }
13522   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13523       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13524       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13525     commentList[index] = (char *) malloc(len + 2);
13526     strncpy(commentList[index], text, len);
13527     commentList[index][len] = '\n';
13528     commentList[index][len + 1] = NULLCHAR;
13529   } else {
13530     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13531     char *p;
13532     commentList[index] = (char *) malloc(len + 7);
13533     safeStrCpy(commentList[index], "{\n", 3);
13534     safeStrCpy(commentList[index]+2, text, len+1);
13535     commentList[index][len+2] = NULLCHAR;
13536     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13537     strcat(commentList[index], "\n}\n");
13538   }
13539 }
13540
13541 void
13542 CrushCRs(text)
13543      char *text;
13544 {
13545   char *p = text;
13546   char *q = text;
13547   char ch;
13548
13549   do {
13550     ch = *p++;
13551     if (ch == '\r') continue;
13552     *q++ = ch;
13553   } while (ch != '\0');
13554 }
13555
13556 void
13557 AppendComment(index, text, addBraces)
13558      int index;
13559      char *text;
13560      Boolean addBraces; // [HGM] braces: tells if we should add {}
13561 {
13562     int oldlen, len;
13563     char *old;
13564
13565 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13566     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13567
13568     CrushCRs(text);
13569     while (*text == '\n') text++;
13570     len = strlen(text);
13571     while (len > 0 && text[len - 1] == '\n') len--;
13572
13573     if (len == 0) return;
13574
13575     if (commentList[index] != NULL) {
13576         old = commentList[index];
13577         oldlen = strlen(old);
13578         while(commentList[index][oldlen-1] ==  '\n')
13579           commentList[index][--oldlen] = NULLCHAR;
13580         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13581         safeStrCpy(commentList[index], old, oldlen + len + 6);
13582         free(old);
13583         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13584         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13585           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13586           while (*text == '\n') { text++; len--; }
13587           commentList[index][--oldlen] = NULLCHAR;
13588       }
13589         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13590         else          strcat(commentList[index], "\n");
13591         strcat(commentList[index], text);
13592         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13593         else          strcat(commentList[index], "\n");
13594     } else {
13595         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13596         if(addBraces)
13597           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13598         else commentList[index][0] = NULLCHAR;
13599         strcat(commentList[index], text);
13600         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13601         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13602     }
13603 }
13604
13605 static char * FindStr( char * text, char * sub_text )
13606 {
13607     char * result = strstr( text, sub_text );
13608
13609     if( result != NULL ) {
13610         result += strlen( sub_text );
13611     }
13612
13613     return result;
13614 }
13615
13616 /* [AS] Try to extract PV info from PGN comment */
13617 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13618 char *GetInfoFromComment( int index, char * text )
13619 {
13620     char * sep = text, *p;
13621
13622     if( text != NULL && index > 0 ) {
13623         int score = 0;
13624         int depth = 0;
13625         int time = -1, sec = 0, deci;
13626         char * s_eval = FindStr( text, "[%eval " );
13627         char * s_emt = FindStr( text, "[%emt " );
13628
13629         if( s_eval != NULL || s_emt != NULL ) {
13630             /* New style */
13631             char delim;
13632
13633             if( s_eval != NULL ) {
13634                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13635                     return text;
13636                 }
13637
13638                 if( delim != ']' ) {
13639                     return text;
13640                 }
13641             }
13642
13643             if( s_emt != NULL ) {
13644             }
13645                 return text;
13646         }
13647         else {
13648             /* We expect something like: [+|-]nnn.nn/dd */
13649             int score_lo = 0;
13650
13651             if(*text != '{') return text; // [HGM] braces: must be normal comment
13652
13653             sep = strchr( text, '/' );
13654             if( sep == NULL || sep < (text+4) ) {
13655                 return text;
13656             }
13657
13658             p = text;
13659             if(p[1] == '(') { // comment starts with PV
13660                p = strchr(p, ')'); // locate end of PV
13661                if(p == NULL || sep < p+5) return text;
13662                // at this point we have something like "{(.*) +0.23/6 ..."
13663                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13664                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13665                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13666             }
13667             time = -1; sec = -1; deci = -1;
13668             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13669                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13670                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13671                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13672                 return text;
13673             }
13674
13675             if( score_lo < 0 || score_lo >= 100 ) {
13676                 return text;
13677             }
13678
13679             if(sec >= 0) time = 600*time + 10*sec; else
13680             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13681
13682             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13683
13684             /* [HGM] PV time: now locate end of PV info */
13685             while( *++sep >= '0' && *sep <= '9'); // strip depth
13686             if(time >= 0)
13687             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13688             if(sec >= 0)
13689             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13690             if(deci >= 0)
13691             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13692             while(*sep == ' ') sep++;
13693         }
13694
13695         if( depth <= 0 ) {
13696             return text;
13697         }
13698
13699         if( time < 0 ) {
13700             time = -1;
13701         }
13702
13703         pvInfoList[index-1].depth = depth;
13704         pvInfoList[index-1].score = score;
13705         pvInfoList[index-1].time  = 10*time; // centi-sec
13706         if(*sep == '}') *sep = 0; else *--sep = '{';
13707         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13708     }
13709     return sep;
13710 }
13711
13712 void
13713 SendToProgram(message, cps)
13714      char *message;
13715      ChessProgramState *cps;
13716 {
13717     int count, outCount, error;
13718     char buf[MSG_SIZ];
13719
13720     if (cps->pr == NULL) return;
13721     Attention(cps);
13722
13723     if (appData.debugMode) {
13724         TimeMark now;
13725         GetTimeMark(&now);
13726         fprintf(debugFP, "%ld >%-6s: %s",
13727                 SubtractTimeMarks(&now, &programStartTime),
13728                 cps->which, message);
13729     }
13730
13731     count = strlen(message);
13732     outCount = OutputToProcess(cps->pr, message, count, &error);
13733     if (outCount < count && !exiting
13734                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13735       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13736         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13737             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13738                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13739                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13740             } else {
13741                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13742             }
13743             gameInfo.resultDetails = StrSave(buf);
13744         }
13745         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13746     }
13747 }
13748
13749 void
13750 ReceiveFromProgram(isr, closure, message, count, error)
13751      InputSourceRef isr;
13752      VOIDSTAR closure;
13753      char *message;
13754      int count;
13755      int error;
13756 {
13757     char *end_str;
13758     char buf[MSG_SIZ];
13759     ChessProgramState *cps = (ChessProgramState *)closure;
13760
13761     if (isr != cps->isr) return; /* Killed intentionally */
13762     if (count <= 0) {
13763         if (count == 0) {
13764             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13765                     _(cps->which), cps->program);
13766         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13767                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13768                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13769                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13770                 } else {
13771                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13772                 }
13773                 gameInfo.resultDetails = StrSave(buf);
13774             }
13775             RemoveInputSource(cps->isr);
13776             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13777         } else {
13778             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13779                     _(cps->which), cps->program);
13780             RemoveInputSource(cps->isr);
13781
13782             /* [AS] Program is misbehaving badly... kill it */
13783             if( count == -2 ) {
13784                 DestroyChildProcess( cps->pr, 9 );
13785                 cps->pr = NoProc;
13786             }
13787
13788             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13789         }
13790         return;
13791     }
13792
13793     if ((end_str = strchr(message, '\r')) != NULL)
13794       *end_str = NULLCHAR;
13795     if ((end_str = strchr(message, '\n')) != NULL)
13796       *end_str = NULLCHAR;
13797
13798     if (appData.debugMode) {
13799         TimeMark now; int print = 1;
13800         char *quote = ""; char c; int i;
13801
13802         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13803                 char start = message[0];
13804                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13805                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13806                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13807                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13808                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13809                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13810                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13811                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13812                    sscanf(message, "hint: %c", &c)!=1 && 
13813                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13814                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13815                     print = (appData.engineComments >= 2);
13816                 }
13817                 message[0] = start; // restore original message
13818         }
13819         if(print) {
13820                 GetTimeMark(&now);
13821                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13822                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13823                         quote,
13824                         message);
13825         }
13826     }
13827
13828     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13829     if (appData.icsEngineAnalyze) {
13830         if (strstr(message, "whisper") != NULL ||
13831              strstr(message, "kibitz") != NULL ||
13832             strstr(message, "tellics") != NULL) return;
13833     }
13834
13835     HandleMachineMove(message, cps);
13836 }
13837
13838
13839 void
13840 SendTimeControl(cps, mps, tc, inc, sd, st)
13841      ChessProgramState *cps;
13842      int mps, inc, sd, st;
13843      long tc;
13844 {
13845     char buf[MSG_SIZ];
13846     int seconds;
13847
13848     if( timeControl_2 > 0 ) {
13849         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13850             tc = timeControl_2;
13851         }
13852     }
13853     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13854     inc /= cps->timeOdds;
13855     st  /= cps->timeOdds;
13856
13857     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13858
13859     if (st > 0) {
13860       /* Set exact time per move, normally using st command */
13861       if (cps->stKludge) {
13862         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13863         seconds = st % 60;
13864         if (seconds == 0) {
13865           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13866         } else {
13867           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13868         }
13869       } else {
13870         snprintf(buf, MSG_SIZ, "st %d\n", st);
13871       }
13872     } else {
13873       /* Set conventional or incremental time control, using level command */
13874       if (seconds == 0) {
13875         /* Note old gnuchess bug -- minutes:seconds used to not work.
13876            Fixed in later versions, but still avoid :seconds
13877            when seconds is 0. */
13878         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13879       } else {
13880         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13881                  seconds, inc/1000.);
13882       }
13883     }
13884     SendToProgram(buf, cps);
13885
13886     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13887     /* Orthogonally, limit search to given depth */
13888     if (sd > 0) {
13889       if (cps->sdKludge) {
13890         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13891       } else {
13892         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13893       }
13894       SendToProgram(buf, cps);
13895     }
13896
13897     if(cps->nps >= 0) { /* [HGM] nps */
13898         if(cps->supportsNPS == FALSE)
13899           cps->nps = -1; // don't use if engine explicitly says not supported!
13900         else {
13901           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13902           SendToProgram(buf, cps);
13903         }
13904     }
13905 }
13906
13907 ChessProgramState *WhitePlayer()
13908 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13909 {
13910     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13911        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13912         return &second;
13913     return &first;
13914 }
13915
13916 void
13917 SendTimeRemaining(cps, machineWhite)
13918      ChessProgramState *cps;
13919      int /*boolean*/ machineWhite;
13920 {
13921     char message[MSG_SIZ];
13922     long time, otime;
13923
13924     /* Note: this routine must be called when the clocks are stopped
13925        or when they have *just* been set or switched; otherwise
13926        it will be off by the time since the current tick started.
13927     */
13928     if (machineWhite) {
13929         time = whiteTimeRemaining / 10;
13930         otime = blackTimeRemaining / 10;
13931     } else {
13932         time = blackTimeRemaining / 10;
13933         otime = whiteTimeRemaining / 10;
13934     }
13935     /* [HGM] translate opponent's time by time-odds factor */
13936     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13937     if (appData.debugMode) {
13938         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13939     }
13940
13941     if (time <= 0) time = 1;
13942     if (otime <= 0) otime = 1;
13943
13944     snprintf(message, MSG_SIZ, "time %ld\n", time);
13945     SendToProgram(message, cps);
13946
13947     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13948     SendToProgram(message, cps);
13949 }
13950
13951 int
13952 BoolFeature(p, name, loc, cps)
13953      char **p;
13954      char *name;
13955      int *loc;
13956      ChessProgramState *cps;
13957 {
13958   char buf[MSG_SIZ];
13959   int len = strlen(name);
13960   int val;
13961
13962   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13963     (*p) += len + 1;
13964     sscanf(*p, "%d", &val);
13965     *loc = (val != 0);
13966     while (**p && **p != ' ')
13967       (*p)++;
13968     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13969     SendToProgram(buf, cps);
13970     return TRUE;
13971   }
13972   return FALSE;
13973 }
13974
13975 int
13976 IntFeature(p, name, loc, cps)
13977      char **p;
13978      char *name;
13979      int *loc;
13980      ChessProgramState *cps;
13981 {
13982   char buf[MSG_SIZ];
13983   int len = strlen(name);
13984   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13985     (*p) += len + 1;
13986     sscanf(*p, "%d", loc);
13987     while (**p && **p != ' ') (*p)++;
13988     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13989     SendToProgram(buf, cps);
13990     return TRUE;
13991   }
13992   return FALSE;
13993 }
13994
13995 int
13996 StringFeature(p, name, loc, cps)
13997      char **p;
13998      char *name;
13999      char loc[];
14000      ChessProgramState *cps;
14001 {
14002   char buf[MSG_SIZ];
14003   int len = strlen(name);
14004   if (strncmp((*p), name, len) == 0
14005       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14006     (*p) += len + 2;
14007     sscanf(*p, "%[^\"]", loc);
14008     while (**p && **p != '\"') (*p)++;
14009     if (**p == '\"') (*p)++;
14010     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14011     SendToProgram(buf, cps);
14012     return TRUE;
14013   }
14014   return FALSE;
14015 }
14016
14017 int
14018 ParseOption(Option *opt, ChessProgramState *cps)
14019 // [HGM] options: process the string that defines an engine option, and determine
14020 // name, type, default value, and allowed value range
14021 {
14022         char *p, *q, buf[MSG_SIZ];
14023         int n, min = (-1)<<31, max = 1<<31, def;
14024
14025         if(p = strstr(opt->name, " -spin ")) {
14026             if((n = sscanf(p, " -spin %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;
14034         } else if((p = strstr(opt->name, " -slider "))) {
14035             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14036             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14037             if(max < min) max = min; // enforce consistency
14038             if(def < min) def = min;
14039             if(def > max) def = max;
14040             opt->value = def;
14041             opt->min = min;
14042             opt->max = max;
14043             opt->type = Spin; // Slider;
14044         } else if((p = strstr(opt->name, " -string "))) {
14045             opt->textValue = p+9;
14046             opt->type = TextBox;
14047         } else if((p = strstr(opt->name, " -file "))) {
14048             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14049             opt->textValue = p+7;
14050             opt->type = FileName; // FileName;
14051         } else if((p = strstr(opt->name, " -path "))) {
14052             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14053             opt->textValue = p+7;
14054             opt->type = PathName; // PathName;
14055         } else if(p = strstr(opt->name, " -check ")) {
14056             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14057             opt->value = (def != 0);
14058             opt->type = CheckBox;
14059         } else if(p = strstr(opt->name, " -combo ")) {
14060             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14061             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14062             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14063             opt->value = n = 0;
14064             while(q = StrStr(q, " /// ")) {
14065                 n++; *q = 0;    // count choices, and null-terminate each of them
14066                 q += 5;
14067                 if(*q == '*') { // remember default, which is marked with * prefix
14068                     q++;
14069                     opt->value = n;
14070                 }
14071                 cps->comboList[cps->comboCnt++] = q;
14072             }
14073             cps->comboList[cps->comboCnt++] = NULL;
14074             opt->max = n + 1;
14075             opt->type = ComboBox;
14076         } else if(p = strstr(opt->name, " -button")) {
14077             opt->type = Button;
14078         } else if(p = strstr(opt->name, " -save")) {
14079             opt->type = SaveButton;
14080         } else return FALSE;
14081         *p = 0; // terminate option name
14082         // now look if the command-line options define a setting for this engine option.
14083         if(cps->optionSettings && cps->optionSettings[0])
14084             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14085         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14086           snprintf(buf, MSG_SIZ, "option %s", p);
14087                 if(p = strstr(buf, ",")) *p = 0;
14088                 if(q = strchr(buf, '=')) switch(opt->type) {
14089                     case ComboBox:
14090                         for(n=0; n<opt->max; n++)
14091                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14092                         break;
14093                     case TextBox:
14094                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14095                         break;
14096                     case Spin:
14097                     case CheckBox:
14098                         opt->value = atoi(q+1);
14099                     default:
14100                         break;
14101                 }
14102                 strcat(buf, "\n");
14103                 SendToProgram(buf, cps);
14104         }
14105         return TRUE;
14106 }
14107
14108 void
14109 FeatureDone(cps, val)
14110      ChessProgramState* cps;
14111      int val;
14112 {
14113   DelayedEventCallback cb = GetDelayedEvent();
14114   if ((cb == InitBackEnd3 && cps == &first) ||
14115       (cb == SettingsMenuIfReady && cps == &second) ||
14116       (cb == TwoMachinesEventIfReady && cps == &second)) {
14117     CancelDelayedEvent();
14118     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14119   }
14120   cps->initDone = val;
14121 }
14122
14123 /* Parse feature command from engine */
14124 void
14125 ParseFeatures(args, cps)
14126      char* args;
14127      ChessProgramState *cps;
14128 {
14129   char *p = args;
14130   char *q;
14131   int val;
14132   char buf[MSG_SIZ];
14133
14134   for (;;) {
14135     while (*p == ' ') p++;
14136     if (*p == NULLCHAR) return;
14137
14138     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14139     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14140     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14141     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14142     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14143     if (BoolFeature(&p, "reuse", &val, cps)) {
14144       /* Engine can disable reuse, but can't enable it if user said no */
14145       if (!val) cps->reuse = FALSE;
14146       continue;
14147     }
14148     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14149     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14150       if (gameMode == TwoMachinesPlay) {
14151         DisplayTwoMachinesTitle();
14152       } else {
14153         DisplayTitle("");
14154       }
14155       continue;
14156     }
14157     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14158     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14159     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14160     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14161     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14162     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14163     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14164     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14165     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14166     if (IntFeature(&p, "done", &val, cps)) {
14167       FeatureDone(cps, val);
14168       continue;
14169     }
14170     /* Added by Tord: */
14171     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14172     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14173     /* End of additions by Tord */
14174
14175     /* [HGM] added features: */
14176     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14177     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14178     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14179     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14180     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14181     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14182     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14183         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14184           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14185             SendToProgram(buf, cps);
14186             continue;
14187         }
14188         if(cps->nrOptions >= MAX_OPTIONS) {
14189             cps->nrOptions--;
14190             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14191             DisplayError(buf, 0);
14192         }
14193         continue;
14194     }
14195     /* End of additions by HGM */
14196
14197     /* unknown feature: complain and skip */
14198     q = p;
14199     while (*q && *q != '=') q++;
14200     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14201     SendToProgram(buf, cps);
14202     p = q;
14203     if (*p == '=') {
14204       p++;
14205       if (*p == '\"') {
14206         p++;
14207         while (*p && *p != '\"') p++;
14208         if (*p == '\"') p++;
14209       } else {
14210         while (*p && *p != ' ') p++;
14211       }
14212     }
14213   }
14214
14215 }
14216
14217 void
14218 PeriodicUpdatesEvent(newState)
14219      int newState;
14220 {
14221     if (newState == appData.periodicUpdates)
14222       return;
14223
14224     appData.periodicUpdates=newState;
14225
14226     /* Display type changes, so update it now */
14227 //    DisplayAnalysis();
14228
14229     /* Get the ball rolling again... */
14230     if (newState) {
14231         AnalysisPeriodicEvent(1);
14232         StartAnalysisClock();
14233     }
14234 }
14235
14236 void
14237 PonderNextMoveEvent(newState)
14238      int newState;
14239 {
14240     if (newState == appData.ponderNextMove) return;
14241     if (gameMode == EditPosition) EditPositionDone(TRUE);
14242     if (newState) {
14243         SendToProgram("hard\n", &first);
14244         if (gameMode == TwoMachinesPlay) {
14245             SendToProgram("hard\n", &second);
14246         }
14247     } else {
14248         SendToProgram("easy\n", &first);
14249         thinkOutput[0] = NULLCHAR;
14250         if (gameMode == TwoMachinesPlay) {
14251             SendToProgram("easy\n", &second);
14252         }
14253     }
14254     appData.ponderNextMove = newState;
14255 }
14256
14257 void
14258 NewSettingEvent(option, feature, command, value)
14259      char *command;
14260      int option, value, *feature;
14261 {
14262     char buf[MSG_SIZ];
14263
14264     if (gameMode == EditPosition) EditPositionDone(TRUE);
14265     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14266     if(feature == NULL || *feature) SendToProgram(buf, &first);
14267     if (gameMode == TwoMachinesPlay) {
14268         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14269     }
14270 }
14271
14272 void
14273 ShowThinkingEvent()
14274 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14275 {
14276     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14277     int newState = appData.showThinking
14278         // [HGM] thinking: other features now need thinking output as well
14279         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14280
14281     if (oldState == newState) return;
14282     oldState = newState;
14283     if (gameMode == EditPosition) EditPositionDone(TRUE);
14284     if (oldState) {
14285         SendToProgram("post\n", &first);
14286         if (gameMode == TwoMachinesPlay) {
14287             SendToProgram("post\n", &second);
14288         }
14289     } else {
14290         SendToProgram("nopost\n", &first);
14291         thinkOutput[0] = NULLCHAR;
14292         if (gameMode == TwoMachinesPlay) {
14293             SendToProgram("nopost\n", &second);
14294         }
14295     }
14296 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14297 }
14298
14299 void
14300 AskQuestionEvent(title, question, replyPrefix, which)
14301      char *title; char *question; char *replyPrefix; char *which;
14302 {
14303   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14304   if (pr == NoProc) return;
14305   AskQuestion(title, question, replyPrefix, pr);
14306 }
14307
14308 void
14309 TypeInEvent(char firstChar)
14310 {
14311     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14312         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14313         gameMode == AnalyzeMode || gameMode == EditGame || \r
14314         gameMode == EditPosition || gameMode == IcsExamining ||\r
14315         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14316         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14317                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14318                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14319         gameMode == Training) PopUpMoveDialog(firstChar);
14320 }
14321
14322 void
14323 TypeInDoneEvent(char *move)
14324 {
14325         Board board;
14326         int n, fromX, fromY, toX, toY;
14327         char promoChar;
14328         ChessMove moveType;\r
14329
14330         // [HGM] FENedit\r
14331         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14332                 EditPositionPasteFEN(move);\r
14333                 return;\r
14334         }\r
14335         // [HGM] movenum: allow move number to be typed in any mode\r
14336         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14337           ToNrEvent(2*n-1);\r
14338           return;\r
14339         }\r
14340
14341       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14342         gameMode != Training) {\r
14343         DisplayMoveError(_("Displayed move is not current"));\r
14344       } else {\r
14345         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14346           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14347         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14348         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14349           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14350           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14351         } else {\r
14352           DisplayMoveError(_("Could not parse move"));\r
14353         }
14354       }\r
14355 }\r
14356
14357 void
14358 DisplayMove(moveNumber)
14359      int moveNumber;
14360 {
14361     char message[MSG_SIZ];
14362     char res[MSG_SIZ];
14363     char cpThinkOutput[MSG_SIZ];
14364
14365     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14366
14367     if (moveNumber == forwardMostMove - 1 ||
14368         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14369
14370         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14371
14372         if (strchr(cpThinkOutput, '\n')) {
14373             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14374         }
14375     } else {
14376         *cpThinkOutput = NULLCHAR;
14377     }
14378
14379     /* [AS] Hide thinking from human user */
14380     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14381         *cpThinkOutput = NULLCHAR;
14382         if( thinkOutput[0] != NULLCHAR ) {
14383             int i;
14384
14385             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14386                 cpThinkOutput[i] = '.';
14387             }
14388             cpThinkOutput[i] = NULLCHAR;
14389             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14390         }
14391     }
14392
14393     if (moveNumber == forwardMostMove - 1 &&
14394         gameInfo.resultDetails != NULL) {
14395         if (gameInfo.resultDetails[0] == NULLCHAR) {
14396           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14397         } else {
14398           snprintf(res, MSG_SIZ, " {%s} %s",
14399                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14400         }
14401     } else {
14402         res[0] = NULLCHAR;
14403     }
14404
14405     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14406         DisplayMessage(res, cpThinkOutput);
14407     } else {
14408       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14409                 WhiteOnMove(moveNumber) ? " " : ".. ",
14410                 parseList[moveNumber], res);
14411         DisplayMessage(message, cpThinkOutput);
14412     }
14413 }
14414
14415 void
14416 DisplayComment(moveNumber, text)
14417      int moveNumber;
14418      char *text;
14419 {
14420     char title[MSG_SIZ];
14421     char buf[8000]; // comment can be long!
14422     int score, depth;
14423
14424     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14425       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14426     } else {
14427       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14428               WhiteOnMove(moveNumber) ? " " : ".. ",
14429               parseList[moveNumber]);
14430     }
14431     // [HGM] PV info: display PV info together with (or as) comment
14432     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14433       if(text == NULL) text = "";
14434       score = pvInfoList[moveNumber].score;
14435       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14436               depth, (pvInfoList[moveNumber].time+50)/100, text);
14437       text = buf;
14438     }
14439     if (text != NULL && (appData.autoDisplayComment || commentUp))
14440         CommentPopUp(title, text);
14441 }
14442
14443 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14444  * might be busy thinking or pondering.  It can be omitted if your
14445  * gnuchess is configured to stop thinking immediately on any user
14446  * input.  However, that gnuchess feature depends on the FIONREAD
14447  * ioctl, which does not work properly on some flavors of Unix.
14448  */
14449 void
14450 Attention(cps)
14451      ChessProgramState *cps;
14452 {
14453 #if ATTENTION
14454     if (!cps->useSigint) return;
14455     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14456     switch (gameMode) {
14457       case MachinePlaysWhite:
14458       case MachinePlaysBlack:
14459       case TwoMachinesPlay:
14460       case IcsPlayingWhite:
14461       case IcsPlayingBlack:
14462       case AnalyzeMode:
14463       case AnalyzeFile:
14464         /* Skip if we know it isn't thinking */
14465         if (!cps->maybeThinking) return;
14466         if (appData.debugMode)
14467           fprintf(debugFP, "Interrupting %s\n", cps->which);
14468         InterruptChildProcess(cps->pr);
14469         cps->maybeThinking = FALSE;
14470         break;
14471       default:
14472         break;
14473     }
14474 #endif /*ATTENTION*/
14475 }
14476
14477 int
14478 CheckFlags()
14479 {
14480     if (whiteTimeRemaining <= 0) {
14481         if (!whiteFlag) {
14482             whiteFlag = TRUE;
14483             if (appData.icsActive) {
14484                 if (appData.autoCallFlag &&
14485                     gameMode == IcsPlayingBlack && !blackFlag) {
14486                   SendToICS(ics_prefix);
14487                   SendToICS("flag\n");
14488                 }
14489             } else {
14490                 if (blackFlag) {
14491                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14492                 } else {
14493                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14494                     if (appData.autoCallFlag) {
14495                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14496                         return TRUE;
14497                     }
14498                 }
14499             }
14500         }
14501     }
14502     if (blackTimeRemaining <= 0) {
14503         if (!blackFlag) {
14504             blackFlag = TRUE;
14505             if (appData.icsActive) {
14506                 if (appData.autoCallFlag &&
14507                     gameMode == IcsPlayingWhite && !whiteFlag) {
14508                   SendToICS(ics_prefix);
14509                   SendToICS("flag\n");
14510                 }
14511             } else {
14512                 if (whiteFlag) {
14513                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14514                 } else {
14515                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14516                     if (appData.autoCallFlag) {
14517                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14518                         return TRUE;
14519                     }
14520                 }
14521             }
14522         }
14523     }
14524     return FALSE;
14525 }
14526
14527 void
14528 CheckTimeControl()
14529 {
14530     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14531         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14532
14533     /*
14534      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14535      */
14536     if ( !WhiteOnMove(forwardMostMove) ) {
14537         /* White made time control */
14538         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14539         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14540         /* [HGM] time odds: correct new time quota for time odds! */
14541                                             / WhitePlayer()->timeOdds;
14542         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14543     } else {
14544         lastBlack -= blackTimeRemaining;
14545         /* Black made time control */
14546         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14547                                             / WhitePlayer()->other->timeOdds;
14548         lastWhite = whiteTimeRemaining;
14549     }
14550 }
14551
14552 void
14553 DisplayBothClocks()
14554 {
14555     int wom = gameMode == EditPosition ?
14556       !blackPlaysFirst : WhiteOnMove(currentMove);
14557     DisplayWhiteClock(whiteTimeRemaining, wom);
14558     DisplayBlackClock(blackTimeRemaining, !wom);
14559 }
14560
14561
14562 /* Timekeeping seems to be a portability nightmare.  I think everyone
14563    has ftime(), but I'm really not sure, so I'm including some ifdefs
14564    to use other calls if you don't.  Clocks will be less accurate if
14565    you have neither ftime nor gettimeofday.
14566 */
14567
14568 /* VS 2008 requires the #include outside of the function */
14569 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14570 #include <sys/timeb.h>
14571 #endif
14572
14573 /* Get the current time as a TimeMark */
14574 void
14575 GetTimeMark(tm)
14576      TimeMark *tm;
14577 {
14578 #if HAVE_GETTIMEOFDAY
14579
14580     struct timeval timeVal;
14581     struct timezone timeZone;
14582
14583     gettimeofday(&timeVal, &timeZone);
14584     tm->sec = (long) timeVal.tv_sec;
14585     tm->ms = (int) (timeVal.tv_usec / 1000L);
14586
14587 #else /*!HAVE_GETTIMEOFDAY*/
14588 #if HAVE_FTIME
14589
14590 // include <sys/timeb.h> / moved to just above start of function
14591     struct timeb timeB;
14592
14593     ftime(&timeB);
14594     tm->sec = (long) timeB.time;
14595     tm->ms = (int) timeB.millitm;
14596
14597 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14598     tm->sec = (long) time(NULL);
14599     tm->ms = 0;
14600 #endif
14601 #endif
14602 }
14603
14604 /* Return the difference in milliseconds between two
14605    time marks.  We assume the difference will fit in a long!
14606 */
14607 long
14608 SubtractTimeMarks(tm2, tm1)
14609      TimeMark *tm2, *tm1;
14610 {
14611     return 1000L*(tm2->sec - tm1->sec) +
14612            (long) (tm2->ms - tm1->ms);
14613 }
14614
14615
14616 /*
14617  * Code to manage the game clocks.
14618  *
14619  * In tournament play, black starts the clock and then white makes a move.
14620  * We give the human user a slight advantage if he is playing white---the
14621  * clocks don't run until he makes his first move, so it takes zero time.
14622  * Also, we don't account for network lag, so we could get out of sync
14623  * with GNU Chess's clock -- but then, referees are always right.
14624  */
14625
14626 static TimeMark tickStartTM;
14627 static long intendedTickLength;
14628
14629 long
14630 NextTickLength(timeRemaining)
14631      long timeRemaining;
14632 {
14633     long nominalTickLength, nextTickLength;
14634
14635     if (timeRemaining > 0L && timeRemaining <= 10000L)
14636       nominalTickLength = 100L;
14637     else
14638       nominalTickLength = 1000L;
14639     nextTickLength = timeRemaining % nominalTickLength;
14640     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14641
14642     return nextTickLength;
14643 }
14644
14645 /* Adjust clock one minute up or down */
14646 void
14647 AdjustClock(Boolean which, int dir)
14648 {
14649     if(which) blackTimeRemaining += 60000*dir;
14650     else      whiteTimeRemaining += 60000*dir;
14651     DisplayBothClocks();
14652 }
14653
14654 /* Stop clocks and reset to a fresh time control */
14655 void
14656 ResetClocks()
14657 {
14658     (void) StopClockTimer();
14659     if (appData.icsActive) {
14660         whiteTimeRemaining = blackTimeRemaining = 0;
14661     } else if (searchTime) {
14662         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14663         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14664     } else { /* [HGM] correct new time quote for time odds */
14665         whiteTC = blackTC = fullTimeControlString;
14666         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14667         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14668     }
14669     if (whiteFlag || blackFlag) {
14670         DisplayTitle("");
14671         whiteFlag = blackFlag = FALSE;
14672     }
14673     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14674     DisplayBothClocks();
14675 }
14676
14677 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14678
14679 /* Decrement running clock by amount of time that has passed */
14680 void
14681 DecrementClocks()
14682 {
14683     long timeRemaining;
14684     long lastTickLength, fudge;
14685     TimeMark now;
14686
14687     if (!appData.clockMode) return;
14688     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14689
14690     GetTimeMark(&now);
14691
14692     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14693
14694     /* Fudge if we woke up a little too soon */
14695     fudge = intendedTickLength - lastTickLength;
14696     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14697
14698     if (WhiteOnMove(forwardMostMove)) {
14699         if(whiteNPS >= 0) lastTickLength = 0;
14700         timeRemaining = whiteTimeRemaining -= lastTickLength;
14701         if(timeRemaining < 0 && !appData.icsActive) {
14702             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14703             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14704                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14705                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14706             }
14707         }
14708         DisplayWhiteClock(whiteTimeRemaining - fudge,
14709                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14710     } else {
14711         if(blackNPS >= 0) lastTickLength = 0;
14712         timeRemaining = blackTimeRemaining -= lastTickLength;
14713         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14714             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14715             if(suddenDeath) {
14716                 blackStartMove = forwardMostMove;
14717                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14718             }
14719         }
14720         DisplayBlackClock(blackTimeRemaining - fudge,
14721                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14722     }
14723     if (CheckFlags()) return;
14724
14725     tickStartTM = now;
14726     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14727     StartClockTimer(intendedTickLength);
14728
14729     /* if the time remaining has fallen below the alarm threshold, sound the
14730      * alarm. if the alarm has sounded and (due to a takeback or time control
14731      * with increment) the time remaining has increased to a level above the
14732      * threshold, reset the alarm so it can sound again.
14733      */
14734
14735     if (appData.icsActive && appData.icsAlarm) {
14736
14737         /* make sure we are dealing with the user's clock */
14738         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14739                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14740            )) return;
14741
14742         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14743             alarmSounded = FALSE;
14744         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14745             PlayAlarmSound();
14746             alarmSounded = TRUE;
14747         }
14748     }
14749 }
14750
14751
14752 /* A player has just moved, so stop the previously running
14753    clock and (if in clock mode) start the other one.
14754    We redisplay both clocks in case we're in ICS mode, because
14755    ICS gives us an update to both clocks after every move.
14756    Note that this routine is called *after* forwardMostMove
14757    is updated, so the last fractional tick must be subtracted
14758    from the color that is *not* on move now.
14759 */
14760 void
14761 SwitchClocks(int newMoveNr)
14762 {
14763     long lastTickLength;
14764     TimeMark now;
14765     int flagged = FALSE;
14766
14767     GetTimeMark(&now);
14768
14769     if (StopClockTimer() && appData.clockMode) {
14770         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14771         if (!WhiteOnMove(forwardMostMove)) {
14772             if(blackNPS >= 0) lastTickLength = 0;
14773             blackTimeRemaining -= lastTickLength;
14774            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14775 //         if(pvInfoList[forwardMostMove].time == -1)
14776                  pvInfoList[forwardMostMove].time =               // use GUI time
14777                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14778         } else {
14779            if(whiteNPS >= 0) lastTickLength = 0;
14780            whiteTimeRemaining -= lastTickLength;
14781            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14782 //         if(pvInfoList[forwardMostMove].time == -1)
14783                  pvInfoList[forwardMostMove].time =
14784                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14785         }
14786         flagged = CheckFlags();
14787     }
14788     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14789     CheckTimeControl();
14790
14791     if (flagged || !appData.clockMode) return;
14792
14793     switch (gameMode) {
14794       case MachinePlaysBlack:
14795       case MachinePlaysWhite:
14796       case BeginningOfGame:
14797         if (pausing) return;
14798         break;
14799
14800       case EditGame:
14801       case PlayFromGameFile:
14802       case IcsExamining:
14803         return;
14804
14805       default:
14806         break;
14807     }
14808
14809     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14810         if(WhiteOnMove(forwardMostMove))
14811              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14812         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14813     }
14814
14815     tickStartTM = now;
14816     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14817       whiteTimeRemaining : blackTimeRemaining);
14818     StartClockTimer(intendedTickLength);
14819 }
14820
14821
14822 /* Stop both clocks */
14823 void
14824 StopClocks()
14825 {
14826     long lastTickLength;
14827     TimeMark now;
14828
14829     if (!StopClockTimer()) return;
14830     if (!appData.clockMode) return;
14831
14832     GetTimeMark(&now);
14833
14834     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14835     if (WhiteOnMove(forwardMostMove)) {
14836         if(whiteNPS >= 0) lastTickLength = 0;
14837         whiteTimeRemaining -= lastTickLength;
14838         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14839     } else {
14840         if(blackNPS >= 0) lastTickLength = 0;
14841         blackTimeRemaining -= lastTickLength;
14842         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14843     }
14844     CheckFlags();
14845 }
14846
14847 /* Start clock of player on move.  Time may have been reset, so
14848    if clock is already running, stop and restart it. */
14849 void
14850 StartClocks()
14851 {
14852     (void) StopClockTimer(); /* in case it was running already */
14853     DisplayBothClocks();
14854     if (CheckFlags()) return;
14855
14856     if (!appData.clockMode) return;
14857     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14858
14859     GetTimeMark(&tickStartTM);
14860     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14861       whiteTimeRemaining : blackTimeRemaining);
14862
14863    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14864     whiteNPS = blackNPS = -1;
14865     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14866        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14867         whiteNPS = first.nps;
14868     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14869        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14870         blackNPS = first.nps;
14871     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14872         whiteNPS = second.nps;
14873     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14874         blackNPS = second.nps;
14875     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14876
14877     StartClockTimer(intendedTickLength);
14878 }
14879
14880 char *
14881 TimeString(ms)
14882      long ms;
14883 {
14884     long second, minute, hour, day;
14885     char *sign = "";
14886     static char buf[32];
14887
14888     if (ms > 0 && ms <= 9900) {
14889       /* convert milliseconds to tenths, rounding up */
14890       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14891
14892       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14893       return buf;
14894     }
14895
14896     /* convert milliseconds to seconds, rounding up */
14897     /* use floating point to avoid strangeness of integer division
14898        with negative dividends on many machines */
14899     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14900
14901     if (second < 0) {
14902         sign = "-";
14903         second = -second;
14904     }
14905
14906     day = second / (60 * 60 * 24);
14907     second = second % (60 * 60 * 24);
14908     hour = second / (60 * 60);
14909     second = second % (60 * 60);
14910     minute = second / 60;
14911     second = second % 60;
14912
14913     if (day > 0)
14914       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14915               sign, day, hour, minute, second);
14916     else if (hour > 0)
14917       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14918     else
14919       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14920
14921     return buf;
14922 }
14923
14924
14925 /*
14926  * This is necessary because some C libraries aren't ANSI C compliant yet.
14927  */
14928 char *
14929 StrStr(string, match)
14930      char *string, *match;
14931 {
14932     int i, length;
14933
14934     length = strlen(match);
14935
14936     for (i = strlen(string) - length; i >= 0; i--, string++)
14937       if (!strncmp(match, string, length))
14938         return string;
14939
14940     return NULL;
14941 }
14942
14943 char *
14944 StrCaseStr(string, match)
14945      char *string, *match;
14946 {
14947     int i, j, length;
14948
14949     length = strlen(match);
14950
14951     for (i = strlen(string) - length; i >= 0; i--, string++) {
14952         for (j = 0; j < length; j++) {
14953             if (ToLower(match[j]) != ToLower(string[j]))
14954               break;
14955         }
14956         if (j == length) return string;
14957     }
14958
14959     return NULL;
14960 }
14961
14962 #ifndef _amigados
14963 int
14964 StrCaseCmp(s1, s2)
14965      char *s1, *s2;
14966 {
14967     char c1, c2;
14968
14969     for (;;) {
14970         c1 = ToLower(*s1++);
14971         c2 = ToLower(*s2++);
14972         if (c1 > c2) return 1;
14973         if (c1 < c2) return -1;
14974         if (c1 == NULLCHAR) return 0;
14975     }
14976 }
14977
14978
14979 int
14980 ToLower(c)
14981      int c;
14982 {
14983     return isupper(c) ? tolower(c) : c;
14984 }
14985
14986
14987 int
14988 ToUpper(c)
14989      int c;
14990 {
14991     return islower(c) ? toupper(c) : c;
14992 }
14993 #endif /* !_amigados    */
14994
14995 char *
14996 StrSave(s)
14997      char *s;
14998 {
14999   char *ret;
15000
15001   if ((ret = (char *) malloc(strlen(s) + 1)))
15002     {
15003       safeStrCpy(ret, s, strlen(s)+1);
15004     }
15005   return ret;
15006 }
15007
15008 char *
15009 StrSavePtr(s, savePtr)
15010      char *s, **savePtr;
15011 {
15012     if (*savePtr) {
15013         free(*savePtr);
15014     }
15015     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15016       safeStrCpy(*savePtr, s, strlen(s)+1);
15017     }
15018     return(*savePtr);
15019 }
15020
15021 char *
15022 PGNDate()
15023 {
15024     time_t clock;
15025     struct tm *tm;
15026     char buf[MSG_SIZ];
15027
15028     clock = time((time_t *)NULL);
15029     tm = localtime(&clock);
15030     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15031             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15032     return StrSave(buf);
15033 }
15034
15035
15036 char *
15037 PositionToFEN(move, overrideCastling)
15038      int move;
15039      char *overrideCastling;
15040 {
15041     int i, j, fromX, fromY, toX, toY;
15042     int whiteToPlay;
15043     char buf[128];
15044     char *p, *q;
15045     int emptycount;
15046     ChessSquare piece;
15047
15048     whiteToPlay = (gameMode == EditPosition) ?
15049       !blackPlaysFirst : (move % 2 == 0);
15050     p = buf;
15051
15052     /* Piece placement data */
15053     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15054         emptycount = 0;
15055         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15056             if (boards[move][i][j] == EmptySquare) {
15057                 emptycount++;
15058             } else { ChessSquare piece = boards[move][i][j];
15059                 if (emptycount > 0) {
15060                     if(emptycount<10) /* [HGM] can be >= 10 */
15061                         *p++ = '0' + emptycount;
15062                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15063                     emptycount = 0;
15064                 }
15065                 if(PieceToChar(piece) == '+') {
15066                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15067                     *p++ = '+';
15068                     piece = (ChessSquare)(DEMOTED piece);
15069                 }
15070                 *p++ = PieceToChar(piece);
15071                 if(p[-1] == '~') {
15072                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15073                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15074                     *p++ = '~';
15075                 }
15076             }
15077         }
15078         if (emptycount > 0) {
15079             if(emptycount<10) /* [HGM] can be >= 10 */
15080                 *p++ = '0' + emptycount;
15081             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15082             emptycount = 0;
15083         }
15084         *p++ = '/';
15085     }
15086     *(p - 1) = ' ';
15087
15088     /* [HGM] print Crazyhouse or Shogi holdings */
15089     if( gameInfo.holdingsWidth ) {
15090         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15091         q = p;
15092         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15093             piece = boards[move][i][BOARD_WIDTH-1];
15094             if( piece != EmptySquare )
15095               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15096                   *p++ = PieceToChar(piece);
15097         }
15098         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15099             piece = boards[move][BOARD_HEIGHT-i-1][0];
15100             if( piece != EmptySquare )
15101               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15102                   *p++ = PieceToChar(piece);
15103         }
15104
15105         if( q == p ) *p++ = '-';
15106         *p++ = ']';
15107         *p++ = ' ';
15108     }
15109
15110     /* Active color */
15111     *p++ = whiteToPlay ? 'w' : 'b';
15112     *p++ = ' ';
15113
15114   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15115     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15116   } else {
15117   if(nrCastlingRights) {
15118      q = p;
15119      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15120        /* [HGM] write directly from rights */
15121            if(boards[move][CASTLING][2] != NoRights &&
15122               boards[move][CASTLING][0] != NoRights   )
15123                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15124            if(boards[move][CASTLING][2] != NoRights &&
15125               boards[move][CASTLING][1] != NoRights   )
15126                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15127            if(boards[move][CASTLING][5] != NoRights &&
15128               boards[move][CASTLING][3] != NoRights   )
15129                 *p++ = boards[move][CASTLING][3] + AAA;
15130            if(boards[move][CASTLING][5] != NoRights &&
15131               boards[move][CASTLING][4] != NoRights   )
15132                 *p++ = boards[move][CASTLING][4] + AAA;
15133      } else {
15134
15135         /* [HGM] write true castling rights */
15136         if( nrCastlingRights == 6 ) {
15137             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15138                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15139             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15140                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15141             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15142                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15143             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15144                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15145         }
15146      }
15147      if (q == p) *p++ = '-'; /* No castling rights */
15148      *p++ = ' ';
15149   }
15150
15151   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15152      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15153     /* En passant target square */
15154     if (move > backwardMostMove) {
15155         fromX = moveList[move - 1][0] - AAA;
15156         fromY = moveList[move - 1][1] - ONE;
15157         toX = moveList[move - 1][2] - AAA;
15158         toY = moveList[move - 1][3] - ONE;
15159         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15160             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15161             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15162             fromX == toX) {
15163             /* 2-square pawn move just happened */
15164             *p++ = toX + AAA;
15165             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15166         } else {
15167             *p++ = '-';
15168         }
15169     } else if(move == backwardMostMove) {
15170         // [HGM] perhaps we should always do it like this, and forget the above?
15171         if((signed char)boards[move][EP_STATUS] >= 0) {
15172             *p++ = boards[move][EP_STATUS] + AAA;
15173             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15174         } else {
15175             *p++ = '-';
15176         }
15177     } else {
15178         *p++ = '-';
15179     }
15180     *p++ = ' ';
15181   }
15182   }
15183
15184     /* [HGM] find reversible plies */
15185     {   int i = 0, j=move;
15186
15187         if (appData.debugMode) { int k;
15188             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15189             for(k=backwardMostMove; k<=forwardMostMove; k++)
15190                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15191
15192         }
15193
15194         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15195         if( j == backwardMostMove ) i += initialRulePlies;
15196         sprintf(p, "%d ", i);
15197         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15198     }
15199     /* Fullmove number */
15200     sprintf(p, "%d", (move / 2) + 1);
15201
15202     return StrSave(buf);
15203 }
15204
15205 Boolean
15206 ParseFEN(board, blackPlaysFirst, fen)
15207     Board board;
15208      int *blackPlaysFirst;
15209      char *fen;
15210 {
15211     int i, j;
15212     char *p, c;
15213     int emptycount;
15214     ChessSquare piece;
15215
15216     p = fen;
15217
15218     /* [HGM] by default clear Crazyhouse holdings, if present */
15219     if(gameInfo.holdingsWidth) {
15220        for(i=0; i<BOARD_HEIGHT; i++) {
15221            board[i][0]             = EmptySquare; /* black holdings */
15222            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15223            board[i][1]             = (ChessSquare) 0; /* black counts */
15224            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15225        }
15226     }
15227
15228     /* Piece placement data */
15229     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15230         j = 0;
15231         for (;;) {
15232             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15233                 if (*p == '/') p++;
15234                 emptycount = gameInfo.boardWidth - j;
15235                 while (emptycount--)
15236                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15237                 break;
15238 #if(BOARD_FILES >= 10)
15239             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15240                 p++; emptycount=10;
15241                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15242                 while (emptycount--)
15243                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15244 #endif
15245             } else if (isdigit(*p)) {
15246                 emptycount = *p++ - '0';
15247                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15248                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15249                 while (emptycount--)
15250                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15251             } else if (*p == '+' || isalpha(*p)) {
15252                 if (j >= gameInfo.boardWidth) return FALSE;
15253                 if(*p=='+') {
15254                     piece = CharToPiece(*++p);
15255                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15256                     piece = (ChessSquare) (PROMOTED piece ); p++;
15257                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15258                 } else piece = CharToPiece(*p++);
15259
15260                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15261                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15262                     piece = (ChessSquare) (PROMOTED piece);
15263                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15264                     p++;
15265                 }
15266                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15267             } else {
15268                 return FALSE;
15269             }
15270         }
15271     }
15272     while (*p == '/' || *p == ' ') p++;
15273
15274     /* [HGM] look for Crazyhouse holdings here */
15275     while(*p==' ') p++;
15276     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15277         if(*p == '[') p++;
15278         if(*p == '-' ) p++; /* empty holdings */ else {
15279             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15280             /* if we would allow FEN reading to set board size, we would   */
15281             /* have to add holdings and shift the board read so far here   */
15282             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15283                 p++;
15284                 if((int) piece >= (int) BlackPawn ) {
15285                     i = (int)piece - (int)BlackPawn;
15286                     i = PieceToNumber((ChessSquare)i);
15287                     if( i >= gameInfo.holdingsSize ) return FALSE;
15288                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15289                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15290                 } else {
15291                     i = (int)piece - (int)WhitePawn;
15292                     i = PieceToNumber((ChessSquare)i);
15293                     if( i >= gameInfo.holdingsSize ) return FALSE;
15294                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15295                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15296                 }
15297             }
15298         }
15299         if(*p == ']') p++;
15300     }
15301
15302     while(*p == ' ') p++;
15303
15304     /* Active color */
15305     c = *p++;
15306     if(appData.colorNickNames) {
15307       if( c == appData.colorNickNames[0] ) c = 'w'; else
15308       if( c == appData.colorNickNames[1] ) c = 'b';
15309     }
15310     switch (c) {
15311       case 'w':
15312         *blackPlaysFirst = FALSE;
15313         break;
15314       case 'b':
15315         *blackPlaysFirst = TRUE;
15316         break;
15317       default:
15318         return FALSE;
15319     }
15320
15321     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15322     /* return the extra info in global variiables             */
15323
15324     /* set defaults in case FEN is incomplete */
15325     board[EP_STATUS] = EP_UNKNOWN;
15326     for(i=0; i<nrCastlingRights; i++ ) {
15327         board[CASTLING][i] =
15328             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15329     }   /* assume possible unless obviously impossible */
15330     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15331     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15332     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15333                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15334     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15335     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15336     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15337                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15338     FENrulePlies = 0;
15339
15340     while(*p==' ') p++;
15341     if(nrCastlingRights) {
15342       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15343           /* castling indicator present, so default becomes no castlings */
15344           for(i=0; i<nrCastlingRights; i++ ) {
15345                  board[CASTLING][i] = NoRights;
15346           }
15347       }
15348       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15349              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15350              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15351              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15352         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15353
15354         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15355             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15356             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15357         }
15358         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15359             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15360         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15361                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15362         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15363                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15364         switch(c) {
15365           case'K':
15366               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15367               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15368               board[CASTLING][2] = whiteKingFile;
15369               break;
15370           case'Q':
15371               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15372               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15373               board[CASTLING][2] = whiteKingFile;
15374               break;
15375           case'k':
15376               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15377               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15378               board[CASTLING][5] = blackKingFile;
15379               break;
15380           case'q':
15381               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15382               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15383               board[CASTLING][5] = blackKingFile;
15384           case '-':
15385               break;
15386           default: /* FRC castlings */
15387               if(c >= 'a') { /* black rights */
15388                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15389                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15390                   if(i == BOARD_RGHT) break;
15391                   board[CASTLING][5] = i;
15392                   c -= AAA;
15393                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15394                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15395                   if(c > i)
15396                       board[CASTLING][3] = c;
15397                   else
15398                       board[CASTLING][4] = c;
15399               } else { /* white rights */
15400                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15401                     if(board[0][i] == WhiteKing) break;
15402                   if(i == BOARD_RGHT) break;
15403                   board[CASTLING][2] = i;
15404                   c -= AAA - 'a' + 'A';
15405                   if(board[0][c] >= WhiteKing) break;
15406                   if(c > i)
15407                       board[CASTLING][0] = c;
15408                   else
15409                       board[CASTLING][1] = c;
15410               }
15411         }
15412       }
15413       for(i=0; i<nrCastlingRights; i++)
15414         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15415     if (appData.debugMode) {
15416         fprintf(debugFP, "FEN castling rights:");
15417         for(i=0; i<nrCastlingRights; i++)
15418         fprintf(debugFP, " %d", board[CASTLING][i]);
15419         fprintf(debugFP, "\n");
15420     }
15421
15422       while(*p==' ') p++;
15423     }
15424
15425     /* read e.p. field in games that know e.p. capture */
15426     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15427        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15428       if(*p=='-') {
15429         p++; board[EP_STATUS] = EP_NONE;
15430       } else {
15431          char c = *p++ - AAA;
15432
15433          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15434          if(*p >= '0' && *p <='9') p++;
15435          board[EP_STATUS] = c;
15436       }
15437     }
15438
15439
15440     if(sscanf(p, "%d", &i) == 1) {
15441         FENrulePlies = i; /* 50-move ply counter */
15442         /* (The move number is still ignored)    */
15443     }
15444
15445     return TRUE;
15446 }
15447
15448 void
15449 EditPositionPasteFEN(char *fen)
15450 {
15451   if (fen != NULL) {
15452     Board initial_position;
15453
15454     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15455       DisplayError(_("Bad FEN position in clipboard"), 0);
15456       return ;
15457     } else {
15458       int savedBlackPlaysFirst = blackPlaysFirst;
15459       EditPositionEvent();
15460       blackPlaysFirst = savedBlackPlaysFirst;
15461       CopyBoard(boards[0], initial_position);
15462       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15463       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15464       DisplayBothClocks();
15465       DrawPosition(FALSE, boards[currentMove]);
15466     }
15467   }
15468 }
15469
15470 static char cseq[12] = "\\   ";
15471
15472 Boolean set_cont_sequence(char *new_seq)
15473 {
15474     int len;
15475     Boolean ret;
15476
15477     // handle bad attempts to set the sequence
15478         if (!new_seq)
15479                 return 0; // acceptable error - no debug
15480
15481     len = strlen(new_seq);
15482     ret = (len > 0) && (len < sizeof(cseq));
15483     if (ret)
15484       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15485     else if (appData.debugMode)
15486       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15487     return ret;
15488 }
15489
15490 /*
15491     reformat a source message so words don't cross the width boundary.  internal
15492     newlines are not removed.  returns the wrapped size (no null character unless
15493     included in source message).  If dest is NULL, only calculate the size required
15494     for the dest buffer.  lp argument indicats line position upon entry, and it's
15495     passed back upon exit.
15496 */
15497 int wrap(char *dest, char *src, int count, int width, int *lp)
15498 {
15499     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15500
15501     cseq_len = strlen(cseq);
15502     old_line = line = *lp;
15503     ansi = len = clen = 0;
15504
15505     for (i=0; i < count; i++)
15506     {
15507         if (src[i] == '\033')
15508             ansi = 1;
15509
15510         // if we hit the width, back up
15511         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15512         {
15513             // store i & len in case the word is too long
15514             old_i = i, old_len = len;
15515
15516             // find the end of the last word
15517             while (i && src[i] != ' ' && src[i] != '\n')
15518             {
15519                 i--;
15520                 len--;
15521             }
15522
15523             // word too long?  restore i & len before splitting it
15524             if ((old_i-i+clen) >= width)
15525             {
15526                 i = old_i;
15527                 len = old_len;
15528             }
15529
15530             // extra space?
15531             if (i && src[i-1] == ' ')
15532                 len--;
15533
15534             if (src[i] != ' ' && src[i] != '\n')
15535             {
15536                 i--;
15537                 if (len)
15538                     len--;
15539             }
15540
15541             // now append the newline and continuation sequence
15542             if (dest)
15543                 dest[len] = '\n';
15544             len++;
15545             if (dest)
15546                 strncpy(dest+len, cseq, cseq_len);
15547             len += cseq_len;
15548             line = cseq_len;
15549             clen = cseq_len;
15550             continue;
15551         }
15552
15553         if (dest)
15554             dest[len] = src[i];
15555         len++;
15556         if (!ansi)
15557             line++;
15558         if (src[i] == '\n')
15559             line = 0;
15560         if (src[i] == 'm')
15561             ansi = 0;
15562     }
15563     if (dest && appData.debugMode)
15564     {
15565         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15566             count, width, line, len, *lp);
15567         show_bytes(debugFP, src, count);
15568         fprintf(debugFP, "\ndest: ");
15569         show_bytes(debugFP, dest, len);
15570         fprintf(debugFP, "\n");
15571     }
15572     *lp = dest ? line : old_line;
15573
15574     return len;
15575 }
15576
15577 // [HGM] vari: routines for shelving variations
15578
15579 void
15580 PushTail(int firstMove, int lastMove)
15581 {
15582         int i, j, nrMoves = lastMove - firstMove;
15583
15584         if(appData.icsActive) { // only in local mode
15585                 forwardMostMove = currentMove; // mimic old ICS behavior
15586                 return;
15587         }
15588         if(storedGames >= MAX_VARIATIONS-1) return;
15589
15590         // push current tail of game on stack
15591         savedResult[storedGames] = gameInfo.result;
15592         savedDetails[storedGames] = gameInfo.resultDetails;
15593         gameInfo.resultDetails = NULL;
15594         savedFirst[storedGames] = firstMove;
15595         savedLast [storedGames] = lastMove;
15596         savedFramePtr[storedGames] = framePtr;
15597         framePtr -= nrMoves; // reserve space for the boards
15598         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15599             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15600             for(j=0; j<MOVE_LEN; j++)
15601                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15602             for(j=0; j<2*MOVE_LEN; j++)
15603                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15604             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15605             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15606             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15607             pvInfoList[firstMove+i-1].depth = 0;
15608             commentList[framePtr+i] = commentList[firstMove+i];
15609             commentList[firstMove+i] = NULL;
15610         }
15611
15612         storedGames++;
15613         forwardMostMove = firstMove; // truncate game so we can start variation
15614         if(storedGames == 1) GreyRevert(FALSE);
15615 }
15616
15617 Boolean
15618 PopTail(Boolean annotate)
15619 {
15620         int i, j, nrMoves;
15621         char buf[8000], moveBuf[20];
15622
15623         if(appData.icsActive) return FALSE; // only in local mode
15624         if(!storedGames) return FALSE; // sanity
15625         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15626
15627         storedGames--;
15628         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15629         nrMoves = savedLast[storedGames] - currentMove;
15630         if(annotate) {
15631                 int cnt = 10;
15632                 if(!WhiteOnMove(currentMove))
15633                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15634                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15635                 for(i=currentMove; i<forwardMostMove; i++) {
15636                         if(WhiteOnMove(i))
15637                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15638                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15639                         strcat(buf, moveBuf);
15640                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15641                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15642                 }
15643                 strcat(buf, ")");
15644         }
15645         for(i=1; i<=nrMoves; i++) { // copy last variation back
15646             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15647             for(j=0; j<MOVE_LEN; j++)
15648                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15649             for(j=0; j<2*MOVE_LEN; j++)
15650                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15651             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15652             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15653             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15654             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15655             commentList[currentMove+i] = commentList[framePtr+i];
15656             commentList[framePtr+i] = NULL;
15657         }
15658         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15659         framePtr = savedFramePtr[storedGames];
15660         gameInfo.result = savedResult[storedGames];
15661         if(gameInfo.resultDetails != NULL) {
15662             free(gameInfo.resultDetails);
15663       }
15664         gameInfo.resultDetails = savedDetails[storedGames];
15665         forwardMostMove = currentMove + nrMoves;
15666         if(storedGames == 0) GreyRevert(TRUE);
15667         return TRUE;
15668 }
15669
15670 void
15671 CleanupTail()
15672 {       // remove all shelved variations
15673         int i;
15674         for(i=0; i<storedGames; i++) {
15675             if(savedDetails[i])
15676                 free(savedDetails[i]);
15677             savedDetails[i] = NULL;
15678         }
15679         for(i=framePtr; i<MAX_MOVES; i++) {
15680                 if(commentList[i]) free(commentList[i]);
15681                 commentList[i] = NULL;
15682         }
15683         framePtr = MAX_MOVES-1;
15684         storedGames = 0;
15685 }
15686
15687 void
15688 LoadVariation(int index, char *text)
15689 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15690         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15691         int level = 0, move;
15692
15693         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15694         // first find outermost bracketing variation
15695         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15696             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15697                 if(*p == '{') wait = '}'; else
15698                 if(*p == '[') wait = ']'; else
15699                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15700                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15701             }
15702             if(*p == wait) wait = NULLCHAR; // closing ]} found
15703             p++;
15704         }
15705         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15706         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15707         end[1] = NULLCHAR; // clip off comment beyond variation
15708         ToNrEvent(currentMove-1);
15709         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15710         // kludge: use ParsePV() to append variation to game
15711         move = currentMove;
15712         ParsePV(start, TRUE);
15713         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15714         ClearPremoveHighlights();
15715         CommentPopDown();
15716         ToNrEvent(currentMove+1);
15717 }
15718