43379fc3f010c25bf6a8fb299e7ddbbd1f2b7cce
[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, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble(u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags(index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
527 };
528
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
534 };
535
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
541 };
542
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackMan, BlackFerz,
547         BlackKing, BlackMan, BlackKnight, BlackRook }
548 };
549
550
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
557 };
558
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
564 };
565
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
571 };
572
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
578 };
579
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
585 };
586
587 #ifdef GOTHIC
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
593 };
594 #else // !GOTHIC
595 #define GothicArray CapablancaArray
596 #endif // !GOTHIC
597
598 #ifdef FALCON
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !FALCON
606 #define FalconArray CapablancaArray
607 #endif // !FALCON
608
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
615
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 };
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
626
627
628 Board initialPosition;
629
630
631 /* Convert str to a rating. Checks for special cases of "----",
632
633    "++++", etc. Also strips ()'s */
634 int
635 string_to_rating(str)
636   char *str;
637 {
638   while(*str && !isdigit(*str)) ++str;
639   if (!*str)
640     return 0;   /* One of the special "no rating" cases */
641   else
642     return atoi(str);
643 }
644
645 void
646 ClearProgramStats()
647 {
648     /* Init programStats */
649     programStats.movelist[0] = 0;
650     programStats.depth = 0;
651     programStats.nr_moves = 0;
652     programStats.moves_left = 0;
653     programStats.nodes = 0;
654     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
655     programStats.score = 0;
656     programStats.got_only_move = 0;
657     programStats.got_fail = 0;
658     programStats.line_is_book = 0;
659 }
660
661 void
662 CommonEngineInit()
663 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664     if (appData.firstPlaysBlack) {
665         first.twoMachinesColor = "black\n";
666         second.twoMachinesColor = "white\n";
667     } else {
668         first.twoMachinesColor = "white\n";
669         second.twoMachinesColor = "black\n";
670     }
671
672     first.other = &second;
673     second.other = &first;
674
675     { float norm = 1;
676         if(appData.timeOddsMode) {
677             norm = appData.timeOdds[0];
678             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
679         }
680         first.timeOdds  = appData.timeOdds[0]/norm;
681         second.timeOdds = appData.timeOdds[1]/norm;
682     }
683
684     if(programVersion) free(programVersion);
685     if (appData.noChessProgram) {
686         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687         sprintf(programVersion, "%s", PACKAGE_STRING);
688     } else {
689       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
692     }
693 }
694
695 void
696 UnloadEngine(ChessProgramState *cps)
697 {
698         /* Kill off first chess program */
699         if (cps->isr != NULL)
700           RemoveInputSource(cps->isr);
701         cps->isr = NULL;
702
703         if (cps->pr != NoProc) {
704             ExitAnalyzeMode();
705             DoSleep( appData.delayBeforeQuit );
706             SendToProgram("quit\n", cps);
707             DoSleep( appData.delayAfterQuit );
708             DestroyChildProcess(cps->pr, cps->useSigterm);
709         }
710         cps->pr = NoProc;
711 }
712
713 void
714 ClearOptions(ChessProgramState *cps)
715 {
716     int i;
717     cps->nrOptions = cps->comboCnt = 0;
718     for(i=0; i<MAX_OPTIONS; i++) {
719         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
720         cps->option[i].textValue = 0;
721     }
722 }
723
724 char *engineNames[] = {
725 "first",
726 "second"
727 };
728
729 InitEngine(ChessProgramState *cps, int n)
730 {   // [HGM] all engine initialiation put in a function that does one engine
731
732     ClearOptions(cps);
733
734     cps->which = engineNames[n];
735     cps->maybeThinking = FALSE;
736     cps->pr = NoProc;
737     cps->isr = NULL;
738     cps->sendTime = 2;
739     cps->sendDrawOffers = 1;
740
741     cps->program = appData.chessProgram[n];
742     cps->host = appData.host[n];
743     cps->dir = appData.directory[n];
744     cps->initString = appData.engInitString[n];
745     cps->computerString = appData.computerString[n];
746     cps->useSigint  = TRUE;
747     cps->useSigterm = TRUE;
748     cps->reuse = appData.reuse[n];
749     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
750     cps->useSetboard = FALSE;
751     cps->useSAN = FALSE;
752     cps->usePing = FALSE;
753     cps->lastPing = 0;
754     cps->lastPong = 0;
755     cps->usePlayother = FALSE;
756     cps->useColors = TRUE;
757     cps->useUsermove = FALSE;
758     cps->sendICS = FALSE;
759     cps->sendName = appData.icsActive;
760     cps->sdKludge = FALSE;
761     cps->stKludge = FALSE;
762     TidyProgramName(cps->program, cps->host, cps->tidy);
763     cps->matchWins = 0;
764     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
765     cps->analysisSupport = 2; /* detect */
766     cps->analyzing = FALSE;
767     cps->initDone = FALSE;
768
769     /* New features added by Tord: */
770     cps->useFEN960 = FALSE;
771     cps->useOOCastle = TRUE;
772     /* End of new features added by Tord. */
773     cps->fenOverride  = appData.fenOverride[n];
774
775     /* [HGM] time odds: set factor for each machine */
776     cps->timeOdds  = appData.timeOdds[n];
777
778     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
779     cps->accumulateTC = appData.accumulateTC[n];
780     cps->maxNrOfSessions = 1;
781
782     /* [HGM] debug */
783     cps->debug = FALSE;
784     cps->supportsNPS = UNKNOWN;
785
786     /* [HGM] options */
787     cps->optionSettings  = appData.engOptions[n];
788
789     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
790     cps->isUCI = appData.isUCI[n]; /* [AS] */
791     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
792
793     if (appData.protocolVersion[n] > PROTOVER
794         || appData.protocolVersion[n] < 1)
795       {
796         char buf[MSG_SIZ];
797         int len;
798
799         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
800                        appData.protocolVersion[n]);
801         if( (len > MSG_SIZ) && appData.debugMode )
802           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
803
804         DisplayFatalError(buf, 0, 2);
805       }
806     else
807       {
808         cps->protocolVersion = appData.protocolVersion[n];
809       }
810
811     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
812 }
813
814 ChessProgramState *savCps;
815
816 void
817 LoadEngine()
818 {
819     int i;
820     if(WaitForEngine(savCps, LoadEngine)) return;
821     CommonEngineInit(); // recalculate time odds
822     if(gameInfo.variant != StringToVariant(appData.variant)) {
823         // we changed variant when loading the engine; this forces us to reset
824         Reset(TRUE, savCps != &first);
825         EditGameEvent(); // for consistency with other path, as Reset changes mode
826     }
827     InitChessProgram(savCps, FALSE);
828     SendToProgram("force\n", savCps);
829     DisplayMessage("", "");
830     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
831     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
832     ThawUI();
833     SetGNUMode();
834 }
835
836 void
837 ReplaceEngine(ChessProgramState *cps, int n)
838 {
839     EditGameEvent();
840     UnloadEngine(cps);
841     appData.noChessProgram = False;
842     appData.clockMode = True;
843     InitEngine(cps, n);
844     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
845     LoadEngine();
846 }
847
848 void
849 InitBackEnd1()
850 {
851     int matched, min, sec;
852
853     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
854     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
855
856     GetTimeMark(&programStartTime);
857     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
858
859     ClearProgramStats();
860     programStats.ok_to_send = 1;
861     programStats.seen_stat = 0;
862
863     /*
864      * Initialize game list
865      */
866     ListNew(&gameList);
867
868
869     /*
870      * Internet chess server status
871      */
872     if (appData.icsActive) {
873         appData.matchMode = FALSE;
874         appData.matchGames = 0;
875 #if ZIPPY
876         appData.noChessProgram = !appData.zippyPlay;
877 #else
878         appData.zippyPlay = FALSE;
879         appData.zippyTalk = FALSE;
880         appData.noChessProgram = TRUE;
881 #endif
882         if (*appData.icsHelper != NULLCHAR) {
883             appData.useTelnet = TRUE;
884             appData.telnetProgram = appData.icsHelper;
885         }
886     } else {
887         appData.zippyTalk = appData.zippyPlay = FALSE;
888     }
889
890     /* [AS] Initialize pv info list [HGM] and game state */
891     {
892         int i, j;
893
894         for( i=0; i<=framePtr; i++ ) {
895             pvInfoList[i].depth = -1;
896             boards[i][EP_STATUS] = EP_NONE;
897             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
898         }
899     }
900
901     /*
902      * Parse timeControl resource
903      */
904     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
905                           appData.movesPerSession)) {
906         char buf[MSG_SIZ];
907         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
908         DisplayFatalError(buf, 0, 2);
909     }
910
911     /*
912      * Parse searchTime resource
913      */
914     if (*appData.searchTime != NULLCHAR) {
915         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
916         if (matched == 1) {
917             searchTime = min * 60;
918         } else if (matched == 2) {
919             searchTime = min * 60 + sec;
920         } else {
921             char buf[MSG_SIZ];
922             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
923             DisplayFatalError(buf, 0, 2);
924         }
925     }
926
927     /* [AS] Adjudication threshold */
928     adjudicateLossThreshold = appData.adjudicateLossThreshold;
929
930     InitEngine(&first, 0);
931     InitEngine(&second, 1);
932     CommonEngineInit();
933
934     if (appData.icsActive) {
935         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
936     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
937         appData.clockMode = FALSE;
938         first.sendTime = second.sendTime = 0;
939     }
940
941 #if ZIPPY
942     /* Override some settings from environment variables, for backward
943        compatibility.  Unfortunately it's not feasible to have the env
944        vars just set defaults, at least in xboard.  Ugh.
945     */
946     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
947       ZippyInit();
948     }
949 #endif
950
951     if (!appData.icsActive) {
952       char buf[MSG_SIZ];
953       int len;
954
955       /* Check for variants that are supported only in ICS mode,
956          or not at all.  Some that are accepted here nevertheless
957          have bugs; see comments below.
958       */
959       VariantClass variant = StringToVariant(appData.variant);
960       switch (variant) {
961       case VariantBughouse:     /* need four players and two boards */
962       case VariantKriegspiel:   /* need to hide pieces and move details */
963         /* case VariantFischeRandom: (Fabien: moved below) */
964         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
965         if( (len > MSG_SIZ) && appData.debugMode )
966           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
967
968         DisplayFatalError(buf, 0, 2);
969         return;
970
971       case VariantUnknown:
972       case VariantLoadable:
973       case Variant29:
974       case Variant30:
975       case Variant31:
976       case Variant32:
977       case Variant33:
978       case Variant34:
979       case Variant35:
980       case Variant36:
981       default:
982         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
983         if( (len > MSG_SIZ) && appData.debugMode )
984           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
985
986         DisplayFatalError(buf, 0, 2);
987         return;
988
989       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
990       case VariantFairy:      /* [HGM] TestLegality definitely off! */
991       case VariantGothic:     /* [HGM] should work */
992       case VariantCapablanca: /* [HGM] should work */
993       case VariantCourier:    /* [HGM] initial forced moves not implemented */
994       case VariantShogi:      /* [HGM] could still mate with pawn drop */
995       case VariantKnightmate: /* [HGM] should work */
996       case VariantCylinder:   /* [HGM] untested */
997       case VariantFalcon:     /* [HGM] untested */
998       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
999                                  offboard interposition not understood */
1000       case VariantNormal:     /* definitely works! */
1001       case VariantWildCastle: /* pieces not automatically shuffled */
1002       case VariantNoCastle:   /* pieces not automatically shuffled */
1003       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1004       case VariantLosers:     /* should work except for win condition,
1005                                  and doesn't know captures are mandatory */
1006       case VariantSuicide:    /* should work except for win condition,
1007                                  and doesn't know captures are mandatory */
1008       case VariantGiveaway:   /* should work except for win condition,
1009                                  and doesn't know captures are mandatory */
1010       case VariantTwoKings:   /* should work */
1011       case VariantAtomic:     /* should work except for win condition */
1012       case Variant3Check:     /* should work except for win condition */
1013       case VariantShatranj:   /* should work except for all win conditions */
1014       case VariantMakruk:     /* should work except for daw countdown */
1015       case VariantBerolina:   /* might work if TestLegality is off */
1016       case VariantCapaRandom: /* should work */
1017       case VariantJanus:      /* should work */
1018       case VariantSuper:      /* experimental */
1019       case VariantGreat:      /* experimental, requires legality testing to be off */
1020       case VariantSChess:     /* S-Chess, should work */
1021       case VariantSpartan:    /* should work */
1022         break;
1023       }
1024     }
1025
1026 }
1027
1028 int NextIntegerFromString( char ** str, long * value )
1029 {
1030     int result = -1;
1031     char * s = *str;
1032
1033     while( *s == ' ' || *s == '\t' ) {
1034         s++;
1035     }
1036
1037     *value = 0;
1038
1039     if( *s >= '0' && *s <= '9' ) {
1040         while( *s >= '0' && *s <= '9' ) {
1041             *value = *value * 10 + (*s - '0');
1042             s++;
1043         }
1044
1045         result = 0;
1046     }
1047
1048     *str = s;
1049
1050     return result;
1051 }
1052
1053 int NextTimeControlFromString( char ** str, long * value )
1054 {
1055     long temp;
1056     int result = NextIntegerFromString( str, &temp );
1057
1058     if( result == 0 ) {
1059         *value = temp * 60; /* Minutes */
1060         if( **str == ':' ) {
1061             (*str)++;
1062             result = NextIntegerFromString( str, &temp );
1063             *value += temp; /* Seconds */
1064         }
1065     }
1066
1067     return result;
1068 }
1069
1070 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1071 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1072     int result = -1, type = 0; long temp, temp2;
1073
1074     if(**str != ':') return -1; // old params remain in force!
1075     (*str)++;
1076     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1077     if( NextIntegerFromString( str, &temp ) ) return -1;
1078     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1079
1080     if(**str != '/') {
1081         /* time only: incremental or sudden-death time control */
1082         if(**str == '+') { /* increment follows; read it */
1083             (*str)++;
1084             if(**str == '!') type = *(*str)++; // Bronstein TC
1085             if(result = NextIntegerFromString( str, &temp2)) return -1;
1086             *inc = temp2 * 1000;
1087             if(**str == '.') { // read fraction of increment
1088                 char *start = ++(*str);
1089                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1090                 temp2 *= 1000;
1091                 while(start++ < *str) temp2 /= 10;
1092                 *inc += temp2;
1093             }
1094         } else *inc = 0;
1095         *moves = 0; *tc = temp * 1000; *incType = type;
1096         return 0;
1097     }
1098
1099     (*str)++; /* classical time control */
1100     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1101
1102     if(result == 0) {
1103         *moves = temp;
1104         *tc    = temp2 * 1000;
1105         *inc   = 0;
1106         *incType = type;
1107     }
1108     return result;
1109 }
1110
1111 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1112 {   /* [HGM] get time to add from the multi-session time-control string */
1113     int incType, moves=1; /* kludge to force reading of first session */
1114     long time, increment;
1115     char *s = tcString;
1116
1117     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1118     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1119     do {
1120         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1121         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1122         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1123         if(movenr == -1) return time;    /* last move before new session     */
1124         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1125         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1126         if(!moves) return increment;     /* current session is incremental   */
1127         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1128     } while(movenr >= -1);               /* try again for next session       */
1129
1130     return 0; // no new time quota on this move
1131 }
1132
1133 int
1134 ParseTimeControl(tc, ti, mps)
1135      char *tc;
1136      float ti;
1137      int mps;
1138 {
1139   long tc1;
1140   long tc2;
1141   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1142   int min, sec=0;
1143
1144   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1145   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1146       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1147   if(ti > 0) {
1148
1149     if(mps)
1150       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1151     else 
1152       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1153   } else {
1154     if(mps)
1155       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1156     else 
1157       snprintf(buf, MSG_SIZ, ":%s", mytc);
1158   }
1159   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1160   
1161   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1162     return FALSE;
1163   }
1164
1165   if( *tc == '/' ) {
1166     /* Parse second time control */
1167     tc++;
1168
1169     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1170       return FALSE;
1171     }
1172
1173     if( tc2 == 0 ) {
1174       return FALSE;
1175     }
1176
1177     timeControl_2 = tc2 * 1000;
1178   }
1179   else {
1180     timeControl_2 = 0;
1181   }
1182
1183   if( tc1 == 0 ) {
1184     return FALSE;
1185   }
1186
1187   timeControl = tc1 * 1000;
1188
1189   if (ti >= 0) {
1190     timeIncrement = ti * 1000;  /* convert to ms */
1191     movesPerSession = 0;
1192   } else {
1193     timeIncrement = 0;
1194     movesPerSession = mps;
1195   }
1196   return TRUE;
1197 }
1198
1199 void
1200 InitBackEnd2()
1201 {
1202     if (appData.debugMode) {
1203         fprintf(debugFP, "%s\n", programVersion);
1204     }
1205
1206     set_cont_sequence(appData.wrapContSeq);
1207     if (appData.matchGames > 0) {
1208         appData.matchMode = TRUE;
1209     } else if (appData.matchMode) {
1210         appData.matchGames = 1;
1211     }
1212     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1213         appData.matchGames = appData.sameColorGames;
1214     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1215         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1216         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1217     }
1218     Reset(TRUE, FALSE);
1219     if (appData.noChessProgram || first.protocolVersion == 1) {
1220       InitBackEnd3();
1221     } else {
1222       /* kludge: allow timeout for initial "feature" commands */
1223       FreezeUI();
1224       DisplayMessage("", _("Starting chess program"));
1225       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1226     }
1227 }
1228
1229 void
1230 MatchEvent(int mode)
1231 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1232         /* Set up machine vs. machine match */
1233         if (appData.noChessProgram) {
1234             DisplayFatalError(_("Can't have a match with no chess programs"),
1235                               0, 2);
1236             return;
1237         }
1238         matchMode = mode;
1239         matchGame = 1;
1240         if (*appData.loadGameFile != NULLCHAR) {
1241             int index = appData.loadGameIndex; // [HGM] autoinc
1242             if(index<0) lastIndex = index = 1;
1243             if (!LoadGameFromFile(appData.loadGameFile,
1244                                   index,
1245                                   appData.loadGameFile, FALSE)) {
1246                 DisplayFatalError(_("Bad game file"), 0, 1);
1247                 return;
1248             }
1249         } else if (*appData.loadPositionFile != NULLCHAR) {
1250             int index = appData.loadPositionIndex; // [HGM] autoinc
1251             if(index<0) lastIndex = index = 1;
1252             if (!LoadPositionFromFile(appData.loadPositionFile,
1253                                       index,
1254                                       appData.loadPositionFile)) {
1255                 DisplayFatalError(_("Bad position file"), 0, 1);
1256                 return;
1257             }
1258         }
1259         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1260         TwoMachinesEvent();
1261 }
1262
1263 void
1264 InitBackEnd3 P((void))
1265 {
1266     GameMode initialMode;
1267     char buf[MSG_SIZ];
1268     int err, len;
1269
1270     InitChessProgram(&first, startedFromSetupPosition);
1271
1272     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1273         free(programVersion);
1274         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1275         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1276     }
1277
1278     if (appData.icsActive) {
1279 #ifdef WIN32
1280         /* [DM] Make a console window if needed [HGM] merged ifs */
1281         ConsoleCreate();
1282 #endif
1283         err = establish();
1284         if (err != 0)
1285           {
1286             if (*appData.icsCommPort != NULLCHAR)
1287               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1288                              appData.icsCommPort);
1289             else
1290               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1291                         appData.icsHost, appData.icsPort);
1292
1293             if( (len > MSG_SIZ) && appData.debugMode )
1294               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1295
1296             DisplayFatalError(buf, err, 1);
1297             return;
1298         }
1299         SetICSMode();
1300         telnetISR =
1301           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1302         fromUserISR =
1303           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1304         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1305             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1306     } else if (appData.noChessProgram) {
1307         SetNCPMode();
1308     } else {
1309         SetGNUMode();
1310     }
1311
1312     if (*appData.cmailGameName != NULLCHAR) {
1313         SetCmailMode();
1314         OpenLoopback(&cmailPR);
1315         cmailISR =
1316           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1317     }
1318
1319     ThawUI();
1320     DisplayMessage("", "");
1321     if (StrCaseCmp(appData.initialMode, "") == 0) {
1322       initialMode = BeginningOfGame;
1323       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1324         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1325         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1326         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1327         ModeHighlight();
1328       }
1329     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1330       initialMode = TwoMachinesPlay;
1331     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1332       initialMode = AnalyzeFile;
1333     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1334       initialMode = AnalyzeMode;
1335     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1336       initialMode = MachinePlaysWhite;
1337     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1338       initialMode = MachinePlaysBlack;
1339     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1340       initialMode = EditGame;
1341     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1342       initialMode = EditPosition;
1343     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1344       initialMode = Training;
1345     } else {
1346       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1347       if( (len > MSG_SIZ) && appData.debugMode )
1348         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1349
1350       DisplayFatalError(buf, 0, 2);
1351       return;
1352     }
1353
1354     if (appData.matchMode) {
1355         MatchEvent(TRUE);
1356     } else if (*appData.cmailGameName != NULLCHAR) {
1357         /* Set up cmail mode */
1358         ReloadCmailMsgEvent(TRUE);
1359     } else {
1360         /* Set up other modes */
1361         if (initialMode == AnalyzeFile) {
1362           if (*appData.loadGameFile == NULLCHAR) {
1363             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1364             return;
1365           }
1366         }
1367         if (*appData.loadGameFile != NULLCHAR) {
1368             (void) LoadGameFromFile(appData.loadGameFile,
1369                                     appData.loadGameIndex,
1370                                     appData.loadGameFile, TRUE);
1371         } else if (*appData.loadPositionFile != NULLCHAR) {
1372             (void) LoadPositionFromFile(appData.loadPositionFile,
1373                                         appData.loadPositionIndex,
1374                                         appData.loadPositionFile);
1375             /* [HGM] try to make self-starting even after FEN load */
1376             /* to allow automatic setup of fairy variants with wtm */
1377             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1378                 gameMode = BeginningOfGame;
1379                 setboardSpoiledMachineBlack = 1;
1380             }
1381             /* [HGM] loadPos: make that every new game uses the setup */
1382             /* from file as long as we do not switch variant          */
1383             if(!blackPlaysFirst) {
1384                 startedFromPositionFile = TRUE;
1385                 CopyBoard(filePosition, boards[0]);
1386             }
1387         }
1388         if (initialMode == AnalyzeMode) {
1389           if (appData.noChessProgram) {
1390             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1391             return;
1392           }
1393           if (appData.icsActive) {
1394             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1395             return;
1396           }
1397           AnalyzeModeEvent();
1398         } else if (initialMode == AnalyzeFile) {
1399           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1400           ShowThinkingEvent();
1401           AnalyzeFileEvent();
1402           AnalysisPeriodicEvent(1);
1403         } else if (initialMode == MachinePlaysWhite) {
1404           if (appData.noChessProgram) {
1405             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1406                               0, 2);
1407             return;
1408           }
1409           if (appData.icsActive) {
1410             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1411                               0, 2);
1412             return;
1413           }
1414           MachineWhiteEvent();
1415         } else if (initialMode == MachinePlaysBlack) {
1416           if (appData.noChessProgram) {
1417             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1418                               0, 2);
1419             return;
1420           }
1421           if (appData.icsActive) {
1422             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1423                               0, 2);
1424             return;
1425           }
1426           MachineBlackEvent();
1427         } else if (initialMode == TwoMachinesPlay) {
1428           if (appData.noChessProgram) {
1429             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1430                               0, 2);
1431             return;
1432           }
1433           if (appData.icsActive) {
1434             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1435                               0, 2);
1436             return;
1437           }
1438           TwoMachinesEvent();
1439         } else if (initialMode == EditGame) {
1440           EditGameEvent();
1441         } else if (initialMode == EditPosition) {
1442           EditPositionEvent();
1443         } else if (initialMode == Training) {
1444           if (*appData.loadGameFile == NULLCHAR) {
1445             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1446             return;
1447           }
1448           TrainingEvent();
1449         }
1450     }
1451 }
1452
1453 /*
1454  * Establish will establish a contact to a remote host.port.
1455  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1456  *  used to talk to the host.
1457  * Returns 0 if okay, error code if not.
1458  */
1459 int
1460 establish()
1461 {
1462     char buf[MSG_SIZ];
1463
1464     if (*appData.icsCommPort != NULLCHAR) {
1465         /* Talk to the host through a serial comm port */
1466         return OpenCommPort(appData.icsCommPort, &icsPR);
1467
1468     } else if (*appData.gateway != NULLCHAR) {
1469         if (*appData.remoteShell == NULLCHAR) {
1470             /* Use the rcmd protocol to run telnet program on a gateway host */
1471             snprintf(buf, sizeof(buf), "%s %s %s",
1472                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1473             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1474
1475         } else {
1476             /* Use the rsh program to run telnet program on a gateway host */
1477             if (*appData.remoteUser == NULLCHAR) {
1478                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1479                         appData.gateway, appData.telnetProgram,
1480                         appData.icsHost, appData.icsPort);
1481             } else {
1482                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1483                         appData.remoteShell, appData.gateway,
1484                         appData.remoteUser, appData.telnetProgram,
1485                         appData.icsHost, appData.icsPort);
1486             }
1487             return StartChildProcess(buf, "", &icsPR);
1488
1489         }
1490     } else if (appData.useTelnet) {
1491         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1492
1493     } else {
1494         /* TCP socket interface differs somewhat between
1495            Unix and NT; handle details in the front end.
1496            */
1497         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1498     }
1499 }
1500
1501 void EscapeExpand(char *p, char *q)
1502 {       // [HGM] initstring: routine to shape up string arguments
1503         while(*p++ = *q++) if(p[-1] == '\\')
1504             switch(*q++) {
1505                 case 'n': p[-1] = '\n'; break;
1506                 case 'r': p[-1] = '\r'; break;
1507                 case 't': p[-1] = '\t'; break;
1508                 case '\\': p[-1] = '\\'; break;
1509                 case 0: *p = 0; return;
1510                 default: p[-1] = q[-1]; break;
1511             }
1512 }
1513
1514 void
1515 show_bytes(fp, buf, count)
1516      FILE *fp;
1517      char *buf;
1518      int count;
1519 {
1520     while (count--) {
1521         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1522             fprintf(fp, "\\%03o", *buf & 0xff);
1523         } else {
1524             putc(*buf, fp);
1525         }
1526         buf++;
1527     }
1528     fflush(fp);
1529 }
1530
1531 /* Returns an errno value */
1532 int
1533 OutputMaybeTelnet(pr, message, count, outError)
1534      ProcRef pr;
1535      char *message;
1536      int count;
1537      int *outError;
1538 {
1539     char buf[8192], *p, *q, *buflim;
1540     int left, newcount, outcount;
1541
1542     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1543         *appData.gateway != NULLCHAR) {
1544         if (appData.debugMode) {
1545             fprintf(debugFP, ">ICS: ");
1546             show_bytes(debugFP, message, count);
1547             fprintf(debugFP, "\n");
1548         }
1549         return OutputToProcess(pr, message, count, outError);
1550     }
1551
1552     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1553     p = message;
1554     q = buf;
1555     left = count;
1556     newcount = 0;
1557     while (left) {
1558         if (q >= buflim) {
1559             if (appData.debugMode) {
1560                 fprintf(debugFP, ">ICS: ");
1561                 show_bytes(debugFP, buf, newcount);
1562                 fprintf(debugFP, "\n");
1563             }
1564             outcount = OutputToProcess(pr, buf, newcount, outError);
1565             if (outcount < newcount) return -1; /* to be sure */
1566             q = buf;
1567             newcount = 0;
1568         }
1569         if (*p == '\n') {
1570             *q++ = '\r';
1571             newcount++;
1572         } else if (((unsigned char) *p) == TN_IAC) {
1573             *q++ = (char) TN_IAC;
1574             newcount ++;
1575         }
1576         *q++ = *p++;
1577         newcount++;
1578         left--;
1579     }
1580     if (appData.debugMode) {
1581         fprintf(debugFP, ">ICS: ");
1582         show_bytes(debugFP, buf, newcount);
1583         fprintf(debugFP, "\n");
1584     }
1585     outcount = OutputToProcess(pr, buf, newcount, outError);
1586     if (outcount < newcount) return -1; /* to be sure */
1587     return count;
1588 }
1589
1590 void
1591 read_from_player(isr, closure, message, count, error)
1592      InputSourceRef isr;
1593      VOIDSTAR closure;
1594      char *message;
1595      int count;
1596      int error;
1597 {
1598     int outError, outCount;
1599     static int gotEof = 0;
1600
1601     /* Pass data read from player on to ICS */
1602     if (count > 0) {
1603         gotEof = 0;
1604         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1605         if (outCount < count) {
1606             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1607         }
1608     } else if (count < 0) {
1609         RemoveInputSource(isr);
1610         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1611     } else if (gotEof++ > 0) {
1612         RemoveInputSource(isr);
1613         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1614     }
1615 }
1616
1617 void
1618 KeepAlive()
1619 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1620     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1621     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1622     SendToICS("date\n");
1623     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1624 }
1625
1626 /* added routine for printf style output to ics */
1627 void ics_printf(char *format, ...)
1628 {
1629     char buffer[MSG_SIZ];
1630     va_list args;
1631
1632     va_start(args, format);
1633     vsnprintf(buffer, sizeof(buffer), format, args);
1634     buffer[sizeof(buffer)-1] = '\0';
1635     SendToICS(buffer);
1636     va_end(args);
1637 }
1638
1639 void
1640 SendToICS(s)
1641      char *s;
1642 {
1643     int count, outCount, outError;
1644
1645     if (icsPR == NULL) return;
1646
1647     count = strlen(s);
1648     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1649     if (outCount < count) {
1650         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1651     }
1652 }
1653
1654 /* This is used for sending logon scripts to the ICS. Sending
1655    without a delay causes problems when using timestamp on ICC
1656    (at least on my machine). */
1657 void
1658 SendToICSDelayed(s,msdelay)
1659      char *s;
1660      long msdelay;
1661 {
1662     int count, outCount, outError;
1663
1664     if (icsPR == NULL) return;
1665
1666     count = strlen(s);
1667     if (appData.debugMode) {
1668         fprintf(debugFP, ">ICS: ");
1669         show_bytes(debugFP, s, count);
1670         fprintf(debugFP, "\n");
1671     }
1672     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1673                                       msdelay);
1674     if (outCount < count) {
1675         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1676     }
1677 }
1678
1679
1680 /* Remove all highlighting escape sequences in s
1681    Also deletes any suffix starting with '('
1682    */
1683 char *
1684 StripHighlightAndTitle(s)
1685      char *s;
1686 {
1687     static char retbuf[MSG_SIZ];
1688     char *p = retbuf;
1689
1690     while (*s != NULLCHAR) {
1691         while (*s == '\033') {
1692             while (*s != NULLCHAR && !isalpha(*s)) s++;
1693             if (*s != NULLCHAR) s++;
1694         }
1695         while (*s != NULLCHAR && *s != '\033') {
1696             if (*s == '(' || *s == '[') {
1697                 *p = NULLCHAR;
1698                 return retbuf;
1699             }
1700             *p++ = *s++;
1701         }
1702     }
1703     *p = NULLCHAR;
1704     return retbuf;
1705 }
1706
1707 /* Remove all highlighting escape sequences in s */
1708 char *
1709 StripHighlight(s)
1710      char *s;
1711 {
1712     static char retbuf[MSG_SIZ];
1713     char *p = retbuf;
1714
1715     while (*s != NULLCHAR) {
1716         while (*s == '\033') {
1717             while (*s != NULLCHAR && !isalpha(*s)) s++;
1718             if (*s != NULLCHAR) s++;
1719         }
1720         while (*s != NULLCHAR && *s != '\033') {
1721             *p++ = *s++;
1722         }
1723     }
1724     *p = NULLCHAR;
1725     return retbuf;
1726 }
1727
1728 char *variantNames[] = VARIANT_NAMES;
1729 char *
1730 VariantName(v)
1731      VariantClass v;
1732 {
1733     return variantNames[v];
1734 }
1735
1736
1737 /* Identify a variant from the strings the chess servers use or the
1738    PGN Variant tag names we use. */
1739 VariantClass
1740 StringToVariant(e)
1741      char *e;
1742 {
1743     char *p;
1744     int wnum = -1;
1745     VariantClass v = VariantNormal;
1746     int i, found = FALSE;
1747     char buf[MSG_SIZ];
1748     int len;
1749
1750     if (!e) return v;
1751
1752     /* [HGM] skip over optional board-size prefixes */
1753     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1754         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1755         while( *e++ != '_');
1756     }
1757
1758     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1759         v = VariantNormal;
1760         found = TRUE;
1761     } else
1762     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1763       if (StrCaseStr(e, variantNames[i])) {
1764         v = (VariantClass) i;
1765         found = TRUE;
1766         break;
1767       }
1768     }
1769
1770     if (!found) {
1771       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1772           || StrCaseStr(e, "wild/fr")
1773           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1774         v = VariantFischeRandom;
1775       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1776                  (i = 1, p = StrCaseStr(e, "w"))) {
1777         p += i;
1778         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1779         if (isdigit(*p)) {
1780           wnum = atoi(p);
1781         } else {
1782           wnum = -1;
1783         }
1784         switch (wnum) {
1785         case 0: /* FICS only, actually */
1786         case 1:
1787           /* Castling legal even if K starts on d-file */
1788           v = VariantWildCastle;
1789           break;
1790         case 2:
1791         case 3:
1792         case 4:
1793           /* Castling illegal even if K & R happen to start in
1794              normal positions. */
1795           v = VariantNoCastle;
1796           break;
1797         case 5:
1798         case 7:
1799         case 8:
1800         case 10:
1801         case 11:
1802         case 12:
1803         case 13:
1804         case 14:
1805         case 15:
1806         case 18:
1807         case 19:
1808           /* Castling legal iff K & R start in normal positions */
1809           v = VariantNormal;
1810           break;
1811         case 6:
1812         case 20:
1813         case 21:
1814           /* Special wilds for position setup; unclear what to do here */
1815           v = VariantLoadable;
1816           break;
1817         case 9:
1818           /* Bizarre ICC game */
1819           v = VariantTwoKings;
1820           break;
1821         case 16:
1822           v = VariantKriegspiel;
1823           break;
1824         case 17:
1825           v = VariantLosers;
1826           break;
1827         case 22:
1828           v = VariantFischeRandom;
1829           break;
1830         case 23:
1831           v = VariantCrazyhouse;
1832           break;
1833         case 24:
1834           v = VariantBughouse;
1835           break;
1836         case 25:
1837           v = Variant3Check;
1838           break;
1839         case 26:
1840           /* Not quite the same as FICS suicide! */
1841           v = VariantGiveaway;
1842           break;
1843         case 27:
1844           v = VariantAtomic;
1845           break;
1846         case 28:
1847           v = VariantShatranj;
1848           break;
1849
1850         /* Temporary names for future ICC types.  The name *will* change in
1851            the next xboard/WinBoard release after ICC defines it. */
1852         case 29:
1853           v = Variant29;
1854           break;
1855         case 30:
1856           v = Variant30;
1857           break;
1858         case 31:
1859           v = Variant31;
1860           break;
1861         case 32:
1862           v = Variant32;
1863           break;
1864         case 33:
1865           v = Variant33;
1866           break;
1867         case 34:
1868           v = Variant34;
1869           break;
1870         case 35:
1871           v = Variant35;
1872           break;
1873         case 36:
1874           v = Variant36;
1875           break;
1876         case 37:
1877           v = VariantShogi;
1878           break;
1879         case 38:
1880           v = VariantXiangqi;
1881           break;
1882         case 39:
1883           v = VariantCourier;
1884           break;
1885         case 40:
1886           v = VariantGothic;
1887           break;
1888         case 41:
1889           v = VariantCapablanca;
1890           break;
1891         case 42:
1892           v = VariantKnightmate;
1893           break;
1894         case 43:
1895           v = VariantFairy;
1896           break;
1897         case 44:
1898           v = VariantCylinder;
1899           break;
1900         case 45:
1901           v = VariantFalcon;
1902           break;
1903         case 46:
1904           v = VariantCapaRandom;
1905           break;
1906         case 47:
1907           v = VariantBerolina;
1908           break;
1909         case 48:
1910           v = VariantJanus;
1911           break;
1912         case 49:
1913           v = VariantSuper;
1914           break;
1915         case 50:
1916           v = VariantGreat;
1917           break;
1918         case -1:
1919           /* Found "wild" or "w" in the string but no number;
1920              must assume it's normal chess. */
1921           v = VariantNormal;
1922           break;
1923         default:
1924           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1925           if( (len > MSG_SIZ) && appData.debugMode )
1926             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1927
1928           DisplayError(buf, 0);
1929           v = VariantUnknown;
1930           break;
1931         }
1932       }
1933     }
1934     if (appData.debugMode) {
1935       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1936               e, wnum, VariantName(v));
1937     }
1938     return v;
1939 }
1940
1941 static int leftover_start = 0, leftover_len = 0;
1942 char star_match[STAR_MATCH_N][MSG_SIZ];
1943
1944 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1945    advance *index beyond it, and set leftover_start to the new value of
1946    *index; else return FALSE.  If pattern contains the character '*', it
1947    matches any sequence of characters not containing '\r', '\n', or the
1948    character following the '*' (if any), and the matched sequence(s) are
1949    copied into star_match.
1950    */
1951 int
1952 looking_at(buf, index, pattern)
1953      char *buf;
1954      int *index;
1955      char *pattern;
1956 {
1957     char *bufp = &buf[*index], *patternp = pattern;
1958     int star_count = 0;
1959     char *matchp = star_match[0];
1960
1961     for (;;) {
1962         if (*patternp == NULLCHAR) {
1963             *index = leftover_start = bufp - buf;
1964             *matchp = NULLCHAR;
1965             return TRUE;
1966         }
1967         if (*bufp == NULLCHAR) return FALSE;
1968         if (*patternp == '*') {
1969             if (*bufp == *(patternp + 1)) {
1970                 *matchp = NULLCHAR;
1971                 matchp = star_match[++star_count];
1972                 patternp += 2;
1973                 bufp++;
1974                 continue;
1975             } else if (*bufp == '\n' || *bufp == '\r') {
1976                 patternp++;
1977                 if (*patternp == NULLCHAR)
1978                   continue;
1979                 else
1980                   return FALSE;
1981             } else {
1982                 *matchp++ = *bufp++;
1983                 continue;
1984             }
1985         }
1986         if (*patternp != *bufp) return FALSE;
1987         patternp++;
1988         bufp++;
1989     }
1990 }
1991
1992 void
1993 SendToPlayer(data, length)
1994      char *data;
1995      int length;
1996 {
1997     int error, outCount;
1998     outCount = OutputToProcess(NoProc, data, length, &error);
1999     if (outCount < length) {
2000         DisplayFatalError(_("Error writing to display"), error, 1);
2001     }
2002 }
2003
2004 void
2005 PackHolding(packed, holding)
2006      char packed[];
2007      char *holding;
2008 {
2009     char *p = holding;
2010     char *q = packed;
2011     int runlength = 0;
2012     int curr = 9999;
2013     do {
2014         if (*p == curr) {
2015             runlength++;
2016         } else {
2017             switch (runlength) {
2018               case 0:
2019                 break;
2020               case 1:
2021                 *q++ = curr;
2022                 break;
2023               case 2:
2024                 *q++ = curr;
2025                 *q++ = curr;
2026                 break;
2027               default:
2028                 sprintf(q, "%d", runlength);
2029                 while (*q) q++;
2030                 *q++ = curr;
2031                 break;
2032             }
2033             runlength = 1;
2034             curr = *p;
2035         }
2036     } while (*p++);
2037     *q = NULLCHAR;
2038 }
2039
2040 /* Telnet protocol requests from the front end */
2041 void
2042 TelnetRequest(ddww, option)
2043      unsigned char ddww, option;
2044 {
2045     unsigned char msg[3];
2046     int outCount, outError;
2047
2048     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2049
2050     if (appData.debugMode) {
2051         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2052         switch (ddww) {
2053           case TN_DO:
2054             ddwwStr = "DO";
2055             break;
2056           case TN_DONT:
2057             ddwwStr = "DONT";
2058             break;
2059           case TN_WILL:
2060             ddwwStr = "WILL";
2061             break;
2062           case TN_WONT:
2063             ddwwStr = "WONT";
2064             break;
2065           default:
2066             ddwwStr = buf1;
2067             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2068             break;
2069         }
2070         switch (option) {
2071           case TN_ECHO:
2072             optionStr = "ECHO";
2073             break;
2074           default:
2075             optionStr = buf2;
2076             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2077             break;
2078         }
2079         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2080     }
2081     msg[0] = TN_IAC;
2082     msg[1] = ddww;
2083     msg[2] = option;
2084     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2085     if (outCount < 3) {
2086         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2087     }
2088 }
2089
2090 void
2091 DoEcho()
2092 {
2093     if (!appData.icsActive) return;
2094     TelnetRequest(TN_DO, TN_ECHO);
2095 }
2096
2097 void
2098 DontEcho()
2099 {
2100     if (!appData.icsActive) return;
2101     TelnetRequest(TN_DONT, TN_ECHO);
2102 }
2103
2104 void
2105 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2106 {
2107     /* put the holdings sent to us by the server on the board holdings area */
2108     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2109     char p;
2110     ChessSquare piece;
2111
2112     if(gameInfo.holdingsWidth < 2)  return;
2113     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2114         return; // prevent overwriting by pre-board holdings
2115
2116     if( (int)lowestPiece >= BlackPawn ) {
2117         holdingsColumn = 0;
2118         countsColumn = 1;
2119         holdingsStartRow = BOARD_HEIGHT-1;
2120         direction = -1;
2121     } else {
2122         holdingsColumn = BOARD_WIDTH-1;
2123         countsColumn = BOARD_WIDTH-2;
2124         holdingsStartRow = 0;
2125         direction = 1;
2126     }
2127
2128     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2129         board[i][holdingsColumn] = EmptySquare;
2130         board[i][countsColumn]   = (ChessSquare) 0;
2131     }
2132     while( (p=*holdings++) != NULLCHAR ) {
2133         piece = CharToPiece( ToUpper(p) );
2134         if(piece == EmptySquare) continue;
2135         /*j = (int) piece - (int) WhitePawn;*/
2136         j = PieceToNumber(piece);
2137         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2138         if(j < 0) continue;               /* should not happen */
2139         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2140         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2141         board[holdingsStartRow+j*direction][countsColumn]++;
2142     }
2143 }
2144
2145
2146 void
2147 VariantSwitch(Board board, VariantClass newVariant)
2148 {
2149    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2150    static Board oldBoard;
2151
2152    startedFromPositionFile = FALSE;
2153    if(gameInfo.variant == newVariant) return;
2154
2155    /* [HGM] This routine is called each time an assignment is made to
2156     * gameInfo.variant during a game, to make sure the board sizes
2157     * are set to match the new variant. If that means adding or deleting
2158     * holdings, we shift the playing board accordingly
2159     * This kludge is needed because in ICS observe mode, we get boards
2160     * of an ongoing game without knowing the variant, and learn about the
2161     * latter only later. This can be because of the move list we requested,
2162     * in which case the game history is refilled from the beginning anyway,
2163     * but also when receiving holdings of a crazyhouse game. In the latter
2164     * case we want to add those holdings to the already received position.
2165     */
2166
2167
2168    if (appData.debugMode) {
2169      fprintf(debugFP, "Switch board from %s to %s\n",
2170              VariantName(gameInfo.variant), VariantName(newVariant));
2171      setbuf(debugFP, NULL);
2172    }
2173    shuffleOpenings = 0;       /* [HGM] shuffle */
2174    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2175    switch(newVariant)
2176      {
2177      case VariantShogi:
2178        newWidth = 9;  newHeight = 9;
2179        gameInfo.holdingsSize = 7;
2180      case VariantBughouse:
2181      case VariantCrazyhouse:
2182        newHoldingsWidth = 2; break;
2183      case VariantGreat:
2184        newWidth = 10;
2185      case VariantSuper:
2186        newHoldingsWidth = 2;
2187        gameInfo.holdingsSize = 8;
2188        break;
2189      case VariantGothic:
2190      case VariantCapablanca:
2191      case VariantCapaRandom:
2192        newWidth = 10;
2193      default:
2194        newHoldingsWidth = gameInfo.holdingsSize = 0;
2195      };
2196
2197    if(newWidth  != gameInfo.boardWidth  ||
2198       newHeight != gameInfo.boardHeight ||
2199       newHoldingsWidth != gameInfo.holdingsWidth ) {
2200
2201      /* shift position to new playing area, if needed */
2202      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2203        for(i=0; i<BOARD_HEIGHT; i++)
2204          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2205            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2206              board[i][j];
2207        for(i=0; i<newHeight; i++) {
2208          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2209          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2210        }
2211      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2212        for(i=0; i<BOARD_HEIGHT; i++)
2213          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2214            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2215              board[i][j];
2216      }
2217      gameInfo.boardWidth  = newWidth;
2218      gameInfo.boardHeight = newHeight;
2219      gameInfo.holdingsWidth = newHoldingsWidth;
2220      gameInfo.variant = newVariant;
2221      InitDrawingSizes(-2, 0);
2222    } else gameInfo.variant = newVariant;
2223    CopyBoard(oldBoard, board);   // remember correctly formatted board
2224      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2225    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2226 }
2227
2228 static int loggedOn = FALSE;
2229
2230 /*-- Game start info cache: --*/
2231 int gs_gamenum;
2232 char gs_kind[MSG_SIZ];
2233 static char player1Name[128] = "";
2234 static char player2Name[128] = "";
2235 static char cont_seq[] = "\n\\   ";
2236 static int player1Rating = -1;
2237 static int player2Rating = -1;
2238 /*----------------------------*/
2239
2240 ColorClass curColor = ColorNormal;
2241 int suppressKibitz = 0;
2242
2243 // [HGM] seekgraph
2244 Boolean soughtPending = FALSE;
2245 Boolean seekGraphUp;
2246 #define MAX_SEEK_ADS 200
2247 #define SQUARE 0x80
2248 char *seekAdList[MAX_SEEK_ADS];
2249 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2250 float tcList[MAX_SEEK_ADS];
2251 char colorList[MAX_SEEK_ADS];
2252 int nrOfSeekAds = 0;
2253 int minRating = 1010, maxRating = 2800;
2254 int hMargin = 10, vMargin = 20, h, w;
2255 extern int squareSize, lineGap;
2256
2257 void
2258 PlotSeekAd(int i)
2259 {
2260         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2261         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2262         if(r < minRating+100 && r >=0 ) r = minRating+100;
2263         if(r > maxRating) r = maxRating;
2264         if(tc < 1.) tc = 1.;
2265         if(tc > 95.) tc = 95.;
2266         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2267         y = ((double)r - minRating)/(maxRating - minRating)
2268             * (h-vMargin-squareSize/8-1) + vMargin;
2269         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2270         if(strstr(seekAdList[i], " u ")) color = 1;
2271         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2272            !strstr(seekAdList[i], "bullet") &&
2273            !strstr(seekAdList[i], "blitz") &&
2274            !strstr(seekAdList[i], "standard") ) color = 2;
2275         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2276         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2277 }
2278
2279 void
2280 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2281 {
2282         char buf[MSG_SIZ], *ext = "";
2283         VariantClass v = StringToVariant(type);
2284         if(strstr(type, "wild")) {
2285             ext = type + 4; // append wild number
2286             if(v == VariantFischeRandom) type = "chess960"; else
2287             if(v == VariantLoadable) type = "setup"; else
2288             type = VariantName(v);
2289         }
2290         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2291         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2292             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2293             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2294             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2295             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2296             seekNrList[nrOfSeekAds] = nr;
2297             zList[nrOfSeekAds] = 0;
2298             seekAdList[nrOfSeekAds++] = StrSave(buf);
2299             if(plot) PlotSeekAd(nrOfSeekAds-1);
2300         }
2301 }
2302
2303 void
2304 EraseSeekDot(int i)
2305 {
2306     int x = xList[i], y = yList[i], d=squareSize/4, k;
2307     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2308     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2309     // now replot every dot that overlapped
2310     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2311         int xx = xList[k], yy = yList[k];
2312         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2313             DrawSeekDot(xx, yy, colorList[k]);
2314     }
2315 }
2316
2317 void
2318 RemoveSeekAd(int nr)
2319 {
2320         int i;
2321         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2322             EraseSeekDot(i);
2323             if(seekAdList[i]) free(seekAdList[i]);
2324             seekAdList[i] = seekAdList[--nrOfSeekAds];
2325             seekNrList[i] = seekNrList[nrOfSeekAds];
2326             ratingList[i] = ratingList[nrOfSeekAds];
2327             colorList[i]  = colorList[nrOfSeekAds];
2328             tcList[i] = tcList[nrOfSeekAds];
2329             xList[i]  = xList[nrOfSeekAds];
2330             yList[i]  = yList[nrOfSeekAds];
2331             zList[i]  = zList[nrOfSeekAds];
2332             seekAdList[nrOfSeekAds] = NULL;
2333             break;
2334         }
2335 }
2336
2337 Boolean
2338 MatchSoughtLine(char *line)
2339 {
2340     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2341     int nr, base, inc, u=0; char dummy;
2342
2343     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2344        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2345        (u=1) &&
2346        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2347         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2348         // match: compact and save the line
2349         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2350         return TRUE;
2351     }
2352     return FALSE;
2353 }
2354
2355 int
2356 DrawSeekGraph()
2357 {
2358     int i;
2359     if(!seekGraphUp) return FALSE;
2360     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2361     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2362
2363     DrawSeekBackground(0, 0, w, h);
2364     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2365     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2366     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2367         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2368         yy = h-1-yy;
2369         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2370         if(i%500 == 0) {
2371             char buf[MSG_SIZ];
2372             snprintf(buf, MSG_SIZ, "%d", i);
2373             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2374         }
2375     }
2376     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2377     for(i=1; i<100; i+=(i<10?1:5)) {
2378         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2379         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2380         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2381             char buf[MSG_SIZ];
2382             snprintf(buf, MSG_SIZ, "%d", i);
2383             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2384         }
2385     }
2386     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2387     return TRUE;
2388 }
2389
2390 int SeekGraphClick(ClickType click, int x, int y, int moving)
2391 {
2392     static int lastDown = 0, displayed = 0, lastSecond;
2393     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2394         if(click == Release || moving) return FALSE;
2395         nrOfSeekAds = 0;
2396         soughtPending = TRUE;
2397         SendToICS(ics_prefix);
2398         SendToICS("sought\n"); // should this be "sought all"?
2399     } else { // issue challenge based on clicked ad
2400         int dist = 10000; int i, closest = 0, second = 0;
2401         for(i=0; i<nrOfSeekAds; i++) {
2402             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2403             if(d < dist) { dist = d; closest = i; }
2404             second += (d - zList[i] < 120); // count in-range ads
2405             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2406         }
2407         if(dist < 120) {
2408             char buf[MSG_SIZ];
2409             second = (second > 1);
2410             if(displayed != closest || second != lastSecond) {
2411                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2412                 lastSecond = second; displayed = closest;
2413             }
2414             if(click == Press) {
2415                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2416                 lastDown = closest;
2417                 return TRUE;
2418             } // on press 'hit', only show info
2419             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2420             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2421             SendToICS(ics_prefix);
2422             SendToICS(buf);
2423             return TRUE; // let incoming board of started game pop down the graph
2424         } else if(click == Release) { // release 'miss' is ignored
2425             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2426             if(moving == 2) { // right up-click
2427                 nrOfSeekAds = 0; // refresh graph
2428                 soughtPending = TRUE;
2429                 SendToICS(ics_prefix);
2430                 SendToICS("sought\n"); // should this be "sought all"?
2431             }
2432             return TRUE;
2433         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2434         // press miss or release hit 'pop down' seek graph
2435         seekGraphUp = FALSE;
2436         DrawPosition(TRUE, NULL);
2437     }
2438     return TRUE;
2439 }
2440
2441 void
2442 read_from_ics(isr, closure, data, count, error)
2443      InputSourceRef isr;
2444      VOIDSTAR closure;
2445      char *data;
2446      int count;
2447      int error;
2448 {
2449 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2450 #define STARTED_NONE 0
2451 #define STARTED_MOVES 1
2452 #define STARTED_BOARD 2
2453 #define STARTED_OBSERVE 3
2454 #define STARTED_HOLDINGS 4
2455 #define STARTED_CHATTER 5
2456 #define STARTED_COMMENT 6
2457 #define STARTED_MOVES_NOHIDE 7
2458
2459     static int started = STARTED_NONE;
2460     static char parse[20000];
2461     static int parse_pos = 0;
2462     static char buf[BUF_SIZE + 1];
2463     static int firstTime = TRUE, intfSet = FALSE;
2464     static ColorClass prevColor = ColorNormal;
2465     static int savingComment = FALSE;
2466     static int cmatch = 0; // continuation sequence match
2467     char *bp;
2468     char str[MSG_SIZ];
2469     int i, oldi;
2470     int buf_len;
2471     int next_out;
2472     int tkind;
2473     int backup;    /* [DM] For zippy color lines */
2474     char *p;
2475     char talker[MSG_SIZ]; // [HGM] chat
2476     int channel;
2477
2478     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2479
2480     if (appData.debugMode) {
2481       if (!error) {
2482         fprintf(debugFP, "<ICS: ");
2483         show_bytes(debugFP, data, count);
2484         fprintf(debugFP, "\n");
2485       }
2486     }
2487
2488     if (appData.debugMode) { int f = forwardMostMove;
2489         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2490                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2491                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2492     }
2493     if (count > 0) {
2494         /* If last read ended with a partial line that we couldn't parse,
2495            prepend it to the new read and try again. */
2496         if (leftover_len > 0) {
2497             for (i=0; i<leftover_len; i++)
2498               buf[i] = buf[leftover_start + i];
2499         }
2500
2501     /* copy new characters into the buffer */
2502     bp = buf + leftover_len;
2503     buf_len=leftover_len;
2504     for (i=0; i<count; i++)
2505     {
2506         // ignore these
2507         if (data[i] == '\r')
2508             continue;
2509
2510         // join lines split by ICS?
2511         if (!appData.noJoin)
2512         {
2513             /*
2514                 Joining just consists of finding matches against the
2515                 continuation sequence, and discarding that sequence
2516                 if found instead of copying it.  So, until a match
2517                 fails, there's nothing to do since it might be the
2518                 complete sequence, and thus, something we don't want
2519                 copied.
2520             */
2521             if (data[i] == cont_seq[cmatch])
2522             {
2523                 cmatch++;
2524                 if (cmatch == strlen(cont_seq))
2525                 {
2526                     cmatch = 0; // complete match.  just reset the counter
2527
2528                     /*
2529                         it's possible for the ICS to not include the space
2530                         at the end of the last word, making our [correct]
2531                         join operation fuse two separate words.  the server
2532                         does this when the space occurs at the width setting.
2533                     */
2534                     if (!buf_len || buf[buf_len-1] != ' ')
2535                     {
2536                         *bp++ = ' ';
2537                         buf_len++;
2538                     }
2539                 }
2540                 continue;
2541             }
2542             else if (cmatch)
2543             {
2544                 /*
2545                     match failed, so we have to copy what matched before
2546                     falling through and copying this character.  In reality,
2547                     this will only ever be just the newline character, but
2548                     it doesn't hurt to be precise.
2549                 */
2550                 strncpy(bp, cont_seq, cmatch);
2551                 bp += cmatch;
2552                 buf_len += cmatch;
2553                 cmatch = 0;
2554             }
2555         }
2556
2557         // copy this char
2558         *bp++ = data[i];
2559         buf_len++;
2560     }
2561
2562         buf[buf_len] = NULLCHAR;
2563 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2564         next_out = 0;
2565         leftover_start = 0;
2566
2567         i = 0;
2568         while (i < buf_len) {
2569             /* Deal with part of the TELNET option negotiation
2570                protocol.  We refuse to do anything beyond the
2571                defaults, except that we allow the WILL ECHO option,
2572                which ICS uses to turn off password echoing when we are
2573                directly connected to it.  We reject this option
2574                if localLineEditing mode is on (always on in xboard)
2575                and we are talking to port 23, which might be a real
2576                telnet server that will try to keep WILL ECHO on permanently.
2577              */
2578             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2579                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2580                 unsigned char option;
2581                 oldi = i;
2582                 switch ((unsigned char) buf[++i]) {
2583                   case TN_WILL:
2584                     if (appData.debugMode)
2585                       fprintf(debugFP, "\n<WILL ");
2586                     switch (option = (unsigned char) buf[++i]) {
2587                       case TN_ECHO:
2588                         if (appData.debugMode)
2589                           fprintf(debugFP, "ECHO ");
2590                         /* Reply only if this is a change, according
2591                            to the protocol rules. */
2592                         if (remoteEchoOption) break;
2593                         if (appData.localLineEditing &&
2594                             atoi(appData.icsPort) == TN_PORT) {
2595                             TelnetRequest(TN_DONT, TN_ECHO);
2596                         } else {
2597                             EchoOff();
2598                             TelnetRequest(TN_DO, TN_ECHO);
2599                             remoteEchoOption = TRUE;
2600                         }
2601                         break;
2602                       default:
2603                         if (appData.debugMode)
2604                           fprintf(debugFP, "%d ", option);
2605                         /* Whatever this is, we don't want it. */
2606                         TelnetRequest(TN_DONT, option);
2607                         break;
2608                     }
2609                     break;
2610                   case TN_WONT:
2611                     if (appData.debugMode)
2612                       fprintf(debugFP, "\n<WONT ");
2613                     switch (option = (unsigned char) buf[++i]) {
2614                       case TN_ECHO:
2615                         if (appData.debugMode)
2616                           fprintf(debugFP, "ECHO ");
2617                         /* Reply only if this is a change, according
2618                            to the protocol rules. */
2619                         if (!remoteEchoOption) break;
2620                         EchoOn();
2621                         TelnetRequest(TN_DONT, TN_ECHO);
2622                         remoteEchoOption = FALSE;
2623                         break;
2624                       default:
2625                         if (appData.debugMode)
2626                           fprintf(debugFP, "%d ", (unsigned char) option);
2627                         /* Whatever this is, it must already be turned
2628                            off, because we never agree to turn on
2629                            anything non-default, so according to the
2630                            protocol rules, we don't reply. */
2631                         break;
2632                     }
2633                     break;
2634                   case TN_DO:
2635                     if (appData.debugMode)
2636                       fprintf(debugFP, "\n<DO ");
2637                     switch (option = (unsigned char) buf[++i]) {
2638                       default:
2639                         /* Whatever this is, we refuse to do it. */
2640                         if (appData.debugMode)
2641                           fprintf(debugFP, "%d ", option);
2642                         TelnetRequest(TN_WONT, option);
2643                         break;
2644                     }
2645                     break;
2646                   case TN_DONT:
2647                     if (appData.debugMode)
2648                       fprintf(debugFP, "\n<DONT ");
2649                     switch (option = (unsigned char) buf[++i]) {
2650                       default:
2651                         if (appData.debugMode)
2652                           fprintf(debugFP, "%d ", option);
2653                         /* Whatever this is, we are already not doing
2654                            it, because we never agree to do anything
2655                            non-default, so according to the protocol
2656                            rules, we don't reply. */
2657                         break;
2658                     }
2659                     break;
2660                   case TN_IAC:
2661                     if (appData.debugMode)
2662                       fprintf(debugFP, "\n<IAC ");
2663                     /* Doubled IAC; pass it through */
2664                     i--;
2665                     break;
2666                   default:
2667                     if (appData.debugMode)
2668                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2669                     /* Drop all other telnet commands on the floor */
2670                     break;
2671                 }
2672                 if (oldi > next_out)
2673                   SendToPlayer(&buf[next_out], oldi - next_out);
2674                 if (++i > next_out)
2675                   next_out = i;
2676                 continue;
2677             }
2678
2679             /* OK, this at least will *usually* work */
2680             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2681                 loggedOn = TRUE;
2682             }
2683
2684             if (loggedOn && !intfSet) {
2685                 if (ics_type == ICS_ICC) {
2686                   snprintf(str, MSG_SIZ,
2687                           "/set-quietly interface %s\n/set-quietly style 12\n",
2688                           programVersion);
2689                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2690                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2691                 } else if (ics_type == ICS_CHESSNET) {
2692                   snprintf(str, MSG_SIZ, "/style 12\n");
2693                 } else {
2694                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2695                   strcat(str, programVersion);
2696                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2697                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2698                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2699 #ifdef WIN32
2700                   strcat(str, "$iset nohighlight 1\n");
2701 #endif
2702                   strcat(str, "$iset lock 1\n$style 12\n");
2703                 }
2704                 SendToICS(str);
2705                 NotifyFrontendLogin();
2706                 intfSet = TRUE;
2707             }
2708
2709             if (started == STARTED_COMMENT) {
2710                 /* Accumulate characters in comment */
2711                 parse[parse_pos++] = buf[i];
2712                 if (buf[i] == '\n') {
2713                     parse[parse_pos] = NULLCHAR;
2714                     if(chattingPartner>=0) {
2715                         char mess[MSG_SIZ];
2716                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2717                         OutputChatMessage(chattingPartner, mess);
2718                         chattingPartner = -1;
2719                         next_out = i+1; // [HGM] suppress printing in ICS window
2720                     } else
2721                     if(!suppressKibitz) // [HGM] kibitz
2722                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2723                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2724                         int nrDigit = 0, nrAlph = 0, j;
2725                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2726                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2727                         parse[parse_pos] = NULLCHAR;
2728                         // try to be smart: if it does not look like search info, it should go to
2729                         // ICS interaction window after all, not to engine-output window.
2730                         for(j=0; j<parse_pos; j++) { // count letters and digits
2731                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2732                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2733                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2734                         }
2735                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2736                             int depth=0; float score;
2737                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2738                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2739                                 pvInfoList[forwardMostMove-1].depth = depth;
2740                                 pvInfoList[forwardMostMove-1].score = 100*score;
2741                             }
2742                             OutputKibitz(suppressKibitz, parse);
2743                         } else {
2744                             char tmp[MSG_SIZ];
2745                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2746                             SendToPlayer(tmp, strlen(tmp));
2747                         }
2748                         next_out = i+1; // [HGM] suppress printing in ICS window
2749                     }
2750                     started = STARTED_NONE;
2751                 } else {
2752                     /* Don't match patterns against characters in comment */
2753                     i++;
2754                     continue;
2755                 }
2756             }
2757             if (started == STARTED_CHATTER) {
2758                 if (buf[i] != '\n') {
2759                     /* Don't match patterns against characters in chatter */
2760                     i++;
2761                     continue;
2762                 }
2763                 started = STARTED_NONE;
2764                 if(suppressKibitz) next_out = i+1;
2765             }
2766
2767             /* Kludge to deal with rcmd protocol */
2768             if (firstTime && looking_at(buf, &i, "\001*")) {
2769                 DisplayFatalError(&buf[1], 0, 1);
2770                 continue;
2771             } else {
2772                 firstTime = FALSE;
2773             }
2774
2775             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2776                 ics_type = ICS_ICC;
2777                 ics_prefix = "/";
2778                 if (appData.debugMode)
2779                   fprintf(debugFP, "ics_type %d\n", ics_type);
2780                 continue;
2781             }
2782             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2783                 ics_type = ICS_FICS;
2784                 ics_prefix = "$";
2785                 if (appData.debugMode)
2786                   fprintf(debugFP, "ics_type %d\n", ics_type);
2787                 continue;
2788             }
2789             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2790                 ics_type = ICS_CHESSNET;
2791                 ics_prefix = "/";
2792                 if (appData.debugMode)
2793                   fprintf(debugFP, "ics_type %d\n", ics_type);
2794                 continue;
2795             }
2796
2797             if (!loggedOn &&
2798                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2799                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2800                  looking_at(buf, &i, "will be \"*\""))) {
2801               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2802               continue;
2803             }
2804
2805             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2806               char buf[MSG_SIZ];
2807               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2808               DisplayIcsInteractionTitle(buf);
2809               have_set_title = TRUE;
2810             }
2811
2812             /* skip finger notes */
2813             if (started == STARTED_NONE &&
2814                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2815                  (buf[i] == '1' && buf[i+1] == '0')) &&
2816                 buf[i+2] == ':' && buf[i+3] == ' ') {
2817               started = STARTED_CHATTER;
2818               i += 3;
2819               continue;
2820             }
2821
2822             oldi = i;
2823             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2824             if(appData.seekGraph) {
2825                 if(soughtPending && MatchSoughtLine(buf+i)) {
2826                     i = strstr(buf+i, "rated") - buf;
2827                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2828                     next_out = leftover_start = i;
2829                     started = STARTED_CHATTER;
2830                     suppressKibitz = TRUE;
2831                     continue;
2832                 }
2833                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2834                         && looking_at(buf, &i, "* ads displayed")) {
2835                     soughtPending = FALSE;
2836                     seekGraphUp = TRUE;
2837                     DrawSeekGraph();
2838                     continue;
2839                 }
2840                 if(appData.autoRefresh) {
2841                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2842                         int s = (ics_type == ICS_ICC); // ICC format differs
2843                         if(seekGraphUp)
2844                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2845                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2846                         looking_at(buf, &i, "*% "); // eat prompt
2847                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2848                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2849                         next_out = i; // suppress
2850                         continue;
2851                     }
2852                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2853                         char *p = star_match[0];
2854                         while(*p) {
2855                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2856                             while(*p && *p++ != ' '); // next
2857                         }
2858                         looking_at(buf, &i, "*% "); // eat prompt
2859                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2860                         next_out = i;
2861                         continue;
2862                     }
2863                 }
2864             }
2865
2866             /* skip formula vars */
2867             if (started == STARTED_NONE &&
2868                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2869               started = STARTED_CHATTER;
2870               i += 3;
2871               continue;
2872             }
2873
2874             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2875             if (appData.autoKibitz && started == STARTED_NONE &&
2876                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2877                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2878                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2879                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2880                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2881                         suppressKibitz = TRUE;
2882                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2883                         next_out = i;
2884                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2885                                 && (gameMode == IcsPlayingWhite)) ||
2886                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2887                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2888                             started = STARTED_CHATTER; // own kibitz we simply discard
2889                         else {
2890                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2891                             parse_pos = 0; parse[0] = NULLCHAR;
2892                             savingComment = TRUE;
2893                             suppressKibitz = gameMode != IcsObserving ? 2 :
2894                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2895                         }
2896                         continue;
2897                 } else
2898                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2899                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2900                          && atoi(star_match[0])) {
2901                     // suppress the acknowledgements of our own autoKibitz
2902                     char *p;
2903                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2904                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2905                     SendToPlayer(star_match[0], strlen(star_match[0]));
2906                     if(looking_at(buf, &i, "*% ")) // eat prompt
2907                         suppressKibitz = FALSE;
2908                     next_out = i;
2909                     continue;
2910                 }
2911             } // [HGM] kibitz: end of patch
2912
2913             // [HGM] chat: intercept tells by users for which we have an open chat window
2914             channel = -1;
2915             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2916                                            looking_at(buf, &i, "* whispers:") ||
2917                                            looking_at(buf, &i, "* kibitzes:") ||
2918                                            looking_at(buf, &i, "* shouts:") ||
2919                                            looking_at(buf, &i, "* c-shouts:") ||
2920                                            looking_at(buf, &i, "--> * ") ||
2921                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2922                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2923                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2924                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2925                 int p;
2926                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2927                 chattingPartner = -1;
2928
2929                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2930                 for(p=0; p<MAX_CHAT; p++) {
2931                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2932                     talker[0] = '['; strcat(talker, "] ");
2933                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2934                     chattingPartner = p; break;
2935                     }
2936                 } else
2937                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2938                 for(p=0; p<MAX_CHAT; p++) {
2939                     if(!strcmp("kibitzes", chatPartner[p])) {
2940                         talker[0] = '['; strcat(talker, "] ");
2941                         chattingPartner = p; break;
2942                     }
2943                 } else
2944                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2945                 for(p=0; p<MAX_CHAT; p++) {
2946                     if(!strcmp("whispers", chatPartner[p])) {
2947                         talker[0] = '['; strcat(talker, "] ");
2948                         chattingPartner = p; break;
2949                     }
2950                 } else
2951                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2952                   if(buf[i-8] == '-' && buf[i-3] == 't')
2953                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2954                     if(!strcmp("c-shouts", chatPartner[p])) {
2955                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2956                         chattingPartner = p; break;
2957                     }
2958                   }
2959                   if(chattingPartner < 0)
2960                   for(p=0; p<MAX_CHAT; p++) {
2961                     if(!strcmp("shouts", chatPartner[p])) {
2962                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2963                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2964                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2965                         chattingPartner = p; break;
2966                     }
2967                   }
2968                 }
2969                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2970                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2971                     talker[0] = 0; Colorize(ColorTell, FALSE);
2972                     chattingPartner = p; break;
2973                 }
2974                 if(chattingPartner<0) i = oldi; else {
2975                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2976                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2977                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978                     started = STARTED_COMMENT;
2979                     parse_pos = 0; parse[0] = NULLCHAR;
2980                     savingComment = 3 + chattingPartner; // counts as TRUE
2981                     suppressKibitz = TRUE;
2982                     continue;
2983                 }
2984             } // [HGM] chat: end of patch
2985
2986           backup = i;
2987             if (appData.zippyTalk || appData.zippyPlay) {
2988                 /* [DM] Backup address for color zippy lines */
2989 #if ZIPPY
2990                if (loggedOn == TRUE)
2991                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2992                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2993 #endif
2994             } // [DM] 'else { ' deleted
2995                 if (
2996                     /* Regular tells and says */
2997                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2998                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2999                     looking_at(buf, &i, "* says: ") ||
3000                     /* Don't color "message" or "messages" output */
3001                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3002                     looking_at(buf, &i, "*. * at *:*: ") ||
3003                     looking_at(buf, &i, "--* (*:*): ") ||
3004                     /* Message notifications (same color as tells) */
3005                     looking_at(buf, &i, "* has left a message ") ||
3006                     looking_at(buf, &i, "* just sent you a message:\n") ||
3007                     /* Whispers and kibitzes */
3008                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3009                     looking_at(buf, &i, "* kibitzes: ") ||
3010                     /* Channel tells */
3011                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3012
3013                   if (tkind == 1 && strchr(star_match[0], ':')) {
3014                       /* Avoid "tells you:" spoofs in channels */
3015                      tkind = 3;
3016                   }
3017                   if (star_match[0][0] == NULLCHAR ||
3018                       strchr(star_match[0], ' ') ||
3019                       (tkind == 3 && strchr(star_match[1], ' '))) {
3020                     /* Reject bogus matches */
3021                     i = oldi;
3022                   } else {
3023                     if (appData.colorize) {
3024                       if (oldi > next_out) {
3025                         SendToPlayer(&buf[next_out], oldi - next_out);
3026                         next_out = oldi;
3027                       }
3028                       switch (tkind) {
3029                       case 1:
3030                         Colorize(ColorTell, FALSE);
3031                         curColor = ColorTell;
3032                         break;
3033                       case 2:
3034                         Colorize(ColorKibitz, FALSE);
3035                         curColor = ColorKibitz;
3036                         break;
3037                       case 3:
3038                         p = strrchr(star_match[1], '(');
3039                         if (p == NULL) {
3040                           p = star_match[1];
3041                         } else {
3042                           p++;
3043                         }
3044                         if (atoi(p) == 1) {
3045                           Colorize(ColorChannel1, FALSE);
3046                           curColor = ColorChannel1;
3047                         } else {
3048                           Colorize(ColorChannel, FALSE);
3049                           curColor = ColorChannel;
3050                         }
3051                         break;
3052                       case 5:
3053                         curColor = ColorNormal;
3054                         break;
3055                       }
3056                     }
3057                     if (started == STARTED_NONE && appData.autoComment &&
3058                         (gameMode == IcsObserving ||
3059                          gameMode == IcsPlayingWhite ||
3060                          gameMode == IcsPlayingBlack)) {
3061                       parse_pos = i - oldi;
3062                       memcpy(parse, &buf[oldi], parse_pos);
3063                       parse[parse_pos] = NULLCHAR;
3064                       started = STARTED_COMMENT;
3065                       savingComment = TRUE;
3066                     } else {
3067                       started = STARTED_CHATTER;
3068                       savingComment = FALSE;
3069                     }
3070                     loggedOn = TRUE;
3071                     continue;
3072                   }
3073                 }
3074
3075                 if (looking_at(buf, &i, "* s-shouts: ") ||
3076                     looking_at(buf, &i, "* c-shouts: ")) {
3077                     if (appData.colorize) {
3078                         if (oldi > next_out) {
3079                             SendToPlayer(&buf[next_out], oldi - next_out);
3080                             next_out = oldi;
3081                         }
3082                         Colorize(ColorSShout, FALSE);
3083                         curColor = ColorSShout;
3084                     }
3085                     loggedOn = TRUE;
3086                     started = STARTED_CHATTER;
3087                     continue;
3088                 }
3089
3090                 if (looking_at(buf, &i, "--->")) {
3091                     loggedOn = TRUE;
3092                     continue;
3093                 }
3094
3095                 if (looking_at(buf, &i, "* shouts: ") ||
3096                     looking_at(buf, &i, "--> ")) {
3097                     if (appData.colorize) {
3098                         if (oldi > next_out) {
3099                             SendToPlayer(&buf[next_out], oldi - next_out);
3100                             next_out = oldi;
3101                         }
3102                         Colorize(ColorShout, FALSE);
3103                         curColor = ColorShout;
3104                     }
3105                     loggedOn = TRUE;
3106                     started = STARTED_CHATTER;
3107                     continue;
3108                 }
3109
3110                 if (looking_at( buf, &i, "Challenge:")) {
3111                     if (appData.colorize) {
3112                         if (oldi > next_out) {
3113                             SendToPlayer(&buf[next_out], oldi - next_out);
3114                             next_out = oldi;
3115                         }
3116                         Colorize(ColorChallenge, FALSE);
3117                         curColor = ColorChallenge;
3118                     }
3119                     loggedOn = TRUE;
3120                     continue;
3121                 }
3122
3123                 if (looking_at(buf, &i, "* offers you") ||
3124                     looking_at(buf, &i, "* offers to be") ||
3125                     looking_at(buf, &i, "* would like to") ||
3126                     looking_at(buf, &i, "* requests to") ||
3127                     looking_at(buf, &i, "Your opponent offers") ||
3128                     looking_at(buf, &i, "Your opponent requests")) {
3129
3130                     if (appData.colorize) {
3131                         if (oldi > next_out) {
3132                             SendToPlayer(&buf[next_out], oldi - next_out);
3133                             next_out = oldi;
3134                         }
3135                         Colorize(ColorRequest, FALSE);
3136                         curColor = ColorRequest;
3137                     }
3138                     continue;
3139                 }
3140
3141                 if (looking_at(buf, &i, "* (*) seeking")) {
3142                     if (appData.colorize) {
3143                         if (oldi > next_out) {
3144                             SendToPlayer(&buf[next_out], oldi - next_out);
3145                             next_out = oldi;
3146                         }
3147                         Colorize(ColorSeek, FALSE);
3148                         curColor = ColorSeek;
3149                     }
3150                     continue;
3151             }
3152
3153           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3154
3155             if (looking_at(buf, &i, "\\   ")) {
3156                 if (prevColor != ColorNormal) {
3157                     if (oldi > next_out) {
3158                         SendToPlayer(&buf[next_out], oldi - next_out);
3159                         next_out = oldi;
3160                     }
3161                     Colorize(prevColor, TRUE);
3162                     curColor = prevColor;
3163                 }
3164                 if (savingComment) {
3165                     parse_pos = i - oldi;
3166                     memcpy(parse, &buf[oldi], parse_pos);
3167                     parse[parse_pos] = NULLCHAR;
3168                     started = STARTED_COMMENT;
3169                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3170                         chattingPartner = savingComment - 3; // kludge to remember the box
3171                 } else {
3172                     started = STARTED_CHATTER;
3173                 }
3174                 continue;
3175             }
3176
3177             if (looking_at(buf, &i, "Black Strength :") ||
3178                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3179                 looking_at(buf, &i, "<10>") ||
3180                 looking_at(buf, &i, "#@#")) {
3181                 /* Wrong board style */
3182                 loggedOn = TRUE;
3183                 SendToICS(ics_prefix);
3184                 SendToICS("set style 12\n");
3185                 SendToICS(ics_prefix);
3186                 SendToICS("refresh\n");
3187                 continue;
3188             }
3189
3190             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3191                 ICSInitScript();
3192                 have_sent_ICS_logon = 1;
3193                 continue;
3194             }
3195
3196             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3197                 (looking_at(buf, &i, "\n<12> ") ||
3198                  looking_at(buf, &i, "<12> "))) {
3199                 loggedOn = TRUE;
3200                 if (oldi > next_out) {
3201                     SendToPlayer(&buf[next_out], oldi - next_out);
3202                 }
3203                 next_out = i;
3204                 started = STARTED_BOARD;
3205                 parse_pos = 0;
3206                 continue;
3207             }
3208
3209             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3210                 looking_at(buf, &i, "<b1> ")) {
3211                 if (oldi > next_out) {
3212                     SendToPlayer(&buf[next_out], oldi - next_out);
3213                 }
3214                 next_out = i;
3215                 started = STARTED_HOLDINGS;
3216                 parse_pos = 0;
3217                 continue;
3218             }
3219
3220             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3221                 loggedOn = TRUE;
3222                 /* Header for a move list -- first line */
3223
3224                 switch (ics_getting_history) {
3225                   case H_FALSE:
3226                     switch (gameMode) {
3227                       case IcsIdle:
3228                       case BeginningOfGame:
3229                         /* User typed "moves" or "oldmoves" while we
3230                            were idle.  Pretend we asked for these
3231                            moves and soak them up so user can step
3232                            through them and/or save them.
3233                            */
3234                         Reset(FALSE, TRUE);
3235                         gameMode = IcsObserving;
3236                         ModeHighlight();
3237                         ics_gamenum = -1;
3238                         ics_getting_history = H_GOT_UNREQ_HEADER;
3239                         break;
3240                       case EditGame: /*?*/
3241                       case EditPosition: /*?*/
3242                         /* Should above feature work in these modes too? */
3243                         /* For now it doesn't */
3244                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3245                         break;
3246                       default:
3247                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3248                         break;
3249                     }
3250                     break;
3251                   case H_REQUESTED:
3252                     /* Is this the right one? */
3253                     if (gameInfo.white && gameInfo.black &&
3254                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3255                         strcmp(gameInfo.black, star_match[2]) == 0) {
3256                         /* All is well */
3257                         ics_getting_history = H_GOT_REQ_HEADER;
3258                     }
3259                     break;
3260                   case H_GOT_REQ_HEADER:
3261                   case H_GOT_UNREQ_HEADER:
3262                   case H_GOT_UNWANTED_HEADER:
3263                   case H_GETTING_MOVES:
3264                     /* Should not happen */
3265                     DisplayError(_("Error gathering move list: two headers"), 0);
3266                     ics_getting_history = H_FALSE;
3267                     break;
3268                 }
3269
3270                 /* Save player ratings into gameInfo if needed */
3271                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3272                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3273                     (gameInfo.whiteRating == -1 ||
3274                      gameInfo.blackRating == -1)) {
3275
3276                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3277                     gameInfo.blackRating = string_to_rating(star_match[3]);
3278                     if (appData.debugMode)
3279                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3280                               gameInfo.whiteRating, gameInfo.blackRating);
3281                 }
3282                 continue;
3283             }
3284
3285             if (looking_at(buf, &i,
3286               "* * match, initial time: * minute*, increment: * second")) {
3287                 /* Header for a move list -- second line */
3288                 /* Initial board will follow if this is a wild game */
3289                 if (gameInfo.event != NULL) free(gameInfo.event);
3290                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3291                 gameInfo.event = StrSave(str);
3292                 /* [HGM] we switched variant. Translate boards if needed. */
3293                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3294                 continue;
3295             }
3296
3297             if (looking_at(buf, &i, "Move  ")) {
3298                 /* Beginning of a move list */
3299                 switch (ics_getting_history) {
3300                   case H_FALSE:
3301                     /* Normally should not happen */
3302                     /* Maybe user hit reset while we were parsing */
3303                     break;
3304                   case H_REQUESTED:
3305                     /* Happens if we are ignoring a move list that is not
3306                      * the one we just requested.  Common if the user
3307                      * tries to observe two games without turning off
3308                      * getMoveList */
3309                     break;
3310                   case H_GETTING_MOVES:
3311                     /* Should not happen */
3312                     DisplayError(_("Error gathering move list: nested"), 0);
3313                     ics_getting_history = H_FALSE;
3314                     break;
3315                   case H_GOT_REQ_HEADER:
3316                     ics_getting_history = H_GETTING_MOVES;
3317                     started = STARTED_MOVES;
3318                     parse_pos = 0;
3319                     if (oldi > next_out) {
3320                         SendToPlayer(&buf[next_out], oldi - next_out);
3321                     }
3322                     break;
3323                   case H_GOT_UNREQ_HEADER:
3324                     ics_getting_history = H_GETTING_MOVES;
3325                     started = STARTED_MOVES_NOHIDE;
3326                     parse_pos = 0;
3327                     break;
3328                   case H_GOT_UNWANTED_HEADER:
3329                     ics_getting_history = H_FALSE;
3330                     break;
3331                 }
3332                 continue;
3333             }
3334
3335             if (looking_at(buf, &i, "% ") ||
3336                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3337                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3338                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3339                     soughtPending = FALSE;
3340                     seekGraphUp = TRUE;
3341                     DrawSeekGraph();
3342                 }
3343                 if(suppressKibitz) next_out = i;
3344                 savingComment = FALSE;
3345                 suppressKibitz = 0;
3346                 switch (started) {
3347                   case STARTED_MOVES:
3348                   case STARTED_MOVES_NOHIDE:
3349                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3350                     parse[parse_pos + i - oldi] = NULLCHAR;
3351                     ParseGameHistory(parse);
3352 #if ZIPPY
3353                     if (appData.zippyPlay && first.initDone) {
3354                         FeedMovesToProgram(&first, forwardMostMove);
3355                         if (gameMode == IcsPlayingWhite) {
3356                             if (WhiteOnMove(forwardMostMove)) {
3357                                 if (first.sendTime) {
3358                                   if (first.useColors) {
3359                                     SendToProgram("black\n", &first);
3360                                   }
3361                                   SendTimeRemaining(&first, TRUE);
3362                                 }
3363                                 if (first.useColors) {
3364                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3365                                 }
3366                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3367                                 first.maybeThinking = TRUE;
3368                             } else {
3369                                 if (first.usePlayother) {
3370                                   if (first.sendTime) {
3371                                     SendTimeRemaining(&first, TRUE);
3372                                   }
3373                                   SendToProgram("playother\n", &first);
3374                                   firstMove = FALSE;
3375                                 } else {
3376                                   firstMove = TRUE;
3377                                 }
3378                             }
3379                         } else if (gameMode == IcsPlayingBlack) {
3380                             if (!WhiteOnMove(forwardMostMove)) {
3381                                 if (first.sendTime) {
3382                                   if (first.useColors) {
3383                                     SendToProgram("white\n", &first);
3384                                   }
3385                                   SendTimeRemaining(&first, FALSE);
3386                                 }
3387                                 if (first.useColors) {
3388                                   SendToProgram("black\n", &first);
3389                                 }
3390                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3391                                 first.maybeThinking = TRUE;
3392                             } else {
3393                                 if (first.usePlayother) {
3394                                   if (first.sendTime) {
3395                                     SendTimeRemaining(&first, FALSE);
3396                                   }
3397                                   SendToProgram("playother\n", &first);
3398                                   firstMove = FALSE;
3399                                 } else {
3400                                   firstMove = TRUE;
3401                                 }
3402                             }
3403                         }
3404                     }
3405 #endif
3406                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3407                         /* Moves came from oldmoves or moves command
3408                            while we weren't doing anything else.
3409                            */
3410                         currentMove = forwardMostMove;
3411                         ClearHighlights();/*!!could figure this out*/
3412                         flipView = appData.flipView;
3413                         DrawPosition(TRUE, boards[currentMove]);
3414                         DisplayBothClocks();
3415                         snprintf(str, MSG_SIZ, "%s vs. %s",
3416                                 gameInfo.white, gameInfo.black);
3417                         DisplayTitle(str);
3418                         gameMode = IcsIdle;
3419                     } else {
3420                         /* Moves were history of an active game */
3421                         if (gameInfo.resultDetails != NULL) {
3422                             free(gameInfo.resultDetails);
3423                             gameInfo.resultDetails = NULL;
3424                         }
3425                     }
3426                     HistorySet(parseList, backwardMostMove,
3427                                forwardMostMove, currentMove-1);
3428                     DisplayMove(currentMove - 1);
3429                     if (started == STARTED_MOVES) next_out = i;
3430                     started = STARTED_NONE;
3431                     ics_getting_history = H_FALSE;
3432                     break;
3433
3434                   case STARTED_OBSERVE:
3435                     started = STARTED_NONE;
3436                     SendToICS(ics_prefix);
3437                     SendToICS("refresh\n");
3438                     break;
3439
3440                   default:
3441                     break;
3442                 }
3443                 if(bookHit) { // [HGM] book: simulate book reply
3444                     static char bookMove[MSG_SIZ]; // a bit generous?
3445
3446                     programStats.nodes = programStats.depth = programStats.time =
3447                     programStats.score = programStats.got_only_move = 0;
3448                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3449
3450                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3451                     strcat(bookMove, bookHit);
3452                     HandleMachineMove(bookMove, &first);
3453                 }
3454                 continue;
3455             }
3456
3457             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3458                  started == STARTED_HOLDINGS ||
3459                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3460                 /* Accumulate characters in move list or board */
3461                 parse[parse_pos++] = buf[i];
3462             }
3463
3464             /* Start of game messages.  Mostly we detect start of game
3465                when the first board image arrives.  On some versions
3466                of the ICS, though, we need to do a "refresh" after starting
3467                to observe in order to get the current board right away. */
3468             if (looking_at(buf, &i, "Adding game * to observation list")) {
3469                 started = STARTED_OBSERVE;
3470                 continue;
3471             }
3472
3473             /* Handle auto-observe */
3474             if (appData.autoObserve &&
3475                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3476                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3477                 char *player;
3478                 /* Choose the player that was highlighted, if any. */
3479                 if (star_match[0][0] == '\033' ||
3480                     star_match[1][0] != '\033') {
3481                     player = star_match[0];
3482                 } else {
3483                     player = star_match[2];
3484                 }
3485                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3486                         ics_prefix, StripHighlightAndTitle(player));
3487                 SendToICS(str);
3488
3489                 /* Save ratings from notify string */
3490                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3491                 player1Rating = string_to_rating(star_match[1]);
3492                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3493                 player2Rating = string_to_rating(star_match[3]);
3494
3495                 if (appData.debugMode)
3496                   fprintf(debugFP,
3497                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3498                           player1Name, player1Rating,
3499                           player2Name, player2Rating);
3500
3501                 continue;
3502             }
3503
3504             /* Deal with automatic examine mode after a game,
3505                and with IcsObserving -> IcsExamining transition */
3506             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3507                 looking_at(buf, &i, "has made you an examiner of game *")) {
3508
3509                 int gamenum = atoi(star_match[0]);
3510                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3511                     gamenum == ics_gamenum) {
3512                     /* We were already playing or observing this game;
3513                        no need to refetch history */
3514                     gameMode = IcsExamining;
3515                     if (pausing) {
3516                         pauseExamForwardMostMove = forwardMostMove;
3517                     } else if (currentMove < forwardMostMove) {
3518                         ForwardInner(forwardMostMove);
3519                     }
3520                 } else {
3521                     /* I don't think this case really can happen */
3522                     SendToICS(ics_prefix);
3523                     SendToICS("refresh\n");
3524                 }
3525                 continue;
3526             }
3527
3528             /* Error messages */
3529 //          if (ics_user_moved) {
3530             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3531                 if (looking_at(buf, &i, "Illegal move") ||
3532                     looking_at(buf, &i, "Not a legal move") ||
3533                     looking_at(buf, &i, "Your king is in check") ||
3534                     looking_at(buf, &i, "It isn't your turn") ||
3535                     looking_at(buf, &i, "It is not your move")) {
3536                     /* Illegal move */
3537                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3538                         currentMove = forwardMostMove-1;
3539                         DisplayMove(currentMove - 1); /* before DMError */
3540                         DrawPosition(FALSE, boards[currentMove]);
3541                         SwitchClocks(forwardMostMove-1); // [HGM] race
3542                         DisplayBothClocks();
3543                     }
3544                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3545                     ics_user_moved = 0;
3546                     continue;
3547                 }
3548             }
3549
3550             if (looking_at(buf, &i, "still have time") ||
3551                 looking_at(buf, &i, "not out of time") ||
3552                 looking_at(buf, &i, "either player is out of time") ||
3553                 looking_at(buf, &i, "has timeseal; checking")) {
3554                 /* We must have called his flag a little too soon */
3555                 whiteFlag = blackFlag = FALSE;
3556                 continue;
3557             }
3558
3559             if (looking_at(buf, &i, "added * seconds to") ||
3560                 looking_at(buf, &i, "seconds were added to")) {
3561                 /* Update the clocks */
3562                 SendToICS(ics_prefix);
3563                 SendToICS("refresh\n");
3564                 continue;
3565             }
3566
3567             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3568                 ics_clock_paused = TRUE;
3569                 StopClocks();
3570                 continue;
3571             }
3572
3573             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3574                 ics_clock_paused = FALSE;
3575                 StartClocks();
3576                 continue;
3577             }
3578
3579             /* Grab player ratings from the Creating: message.
3580                Note we have to check for the special case when
3581                the ICS inserts things like [white] or [black]. */
3582             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3583                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3584                 /* star_matches:
3585                    0    player 1 name (not necessarily white)
3586                    1    player 1 rating
3587                    2    empty, white, or black (IGNORED)
3588                    3    player 2 name (not necessarily black)
3589                    4    player 2 rating
3590
3591                    The names/ratings are sorted out when the game
3592                    actually starts (below).
3593                 */
3594                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3595                 player1Rating = string_to_rating(star_match[1]);
3596                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3597                 player2Rating = string_to_rating(star_match[4]);
3598
3599                 if (appData.debugMode)
3600                   fprintf(debugFP,
3601                           "Ratings from 'Creating:' %s %d, %s %d\n",
3602                           player1Name, player1Rating,
3603                           player2Name, player2Rating);
3604
3605                 continue;
3606             }
3607
3608             /* Improved generic start/end-of-game messages */
3609             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3610                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3611                 /* If tkind == 0: */
3612                 /* star_match[0] is the game number */
3613                 /*           [1] is the white player's name */
3614                 /*           [2] is the black player's name */
3615                 /* For end-of-game: */
3616                 /*           [3] is the reason for the game end */
3617                 /*           [4] is a PGN end game-token, preceded by " " */
3618                 /* For start-of-game: */
3619                 /*           [3] begins with "Creating" or "Continuing" */
3620                 /*           [4] is " *" or empty (don't care). */
3621                 int gamenum = atoi(star_match[0]);
3622                 char *whitename, *blackname, *why, *endtoken;
3623                 ChessMove endtype = EndOfFile;
3624
3625                 if (tkind == 0) {
3626                   whitename = star_match[1];
3627                   blackname = star_match[2];
3628                   why = star_match[3];
3629                   endtoken = star_match[4];
3630                 } else {
3631                   whitename = star_match[1];
3632                   blackname = star_match[3];
3633                   why = star_match[5];
3634                   endtoken = star_match[6];
3635                 }
3636
3637                 /* Game start messages */
3638                 if (strncmp(why, "Creating ", 9) == 0 ||
3639                     strncmp(why, "Continuing ", 11) == 0) {
3640                     gs_gamenum = gamenum;
3641                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3642                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3643 #if ZIPPY
3644                     if (appData.zippyPlay) {
3645                         ZippyGameStart(whitename, blackname);
3646                     }
3647 #endif /*ZIPPY*/
3648                     partnerBoardValid = FALSE; // [HGM] bughouse
3649                     continue;
3650                 }
3651
3652                 /* Game end messages */
3653                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3654                     ics_gamenum != gamenum) {
3655                     continue;
3656                 }
3657                 while (endtoken[0] == ' ') endtoken++;
3658                 switch (endtoken[0]) {
3659                   case '*':
3660                   default:
3661                     endtype = GameUnfinished;
3662                     break;
3663                   case '0':
3664                     endtype = BlackWins;
3665                     break;
3666                   case '1':
3667                     if (endtoken[1] == '/')
3668                       endtype = GameIsDrawn;
3669                     else
3670                       endtype = WhiteWins;
3671                     break;
3672                 }
3673                 GameEnds(endtype, why, GE_ICS);
3674 #if ZIPPY
3675                 if (appData.zippyPlay && first.initDone) {
3676                     ZippyGameEnd(endtype, why);
3677                     if (first.pr == NULL) {
3678                       /* Start the next process early so that we'll
3679                          be ready for the next challenge */
3680                       StartChessProgram(&first);
3681                     }
3682                     /* Send "new" early, in case this command takes
3683                        a long time to finish, so that we'll be ready
3684                        for the next challenge. */
3685                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3686                     Reset(TRUE, TRUE);
3687                 }
3688 #endif /*ZIPPY*/
3689                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3690                 continue;
3691             }
3692
3693             if (looking_at(buf, &i, "Removing game * from observation") ||
3694                 looking_at(buf, &i, "no longer observing game *") ||
3695                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3696                 if (gameMode == IcsObserving &&
3697                     atoi(star_match[0]) == ics_gamenum)
3698                   {
3699                       /* icsEngineAnalyze */
3700                       if (appData.icsEngineAnalyze) {
3701                             ExitAnalyzeMode();
3702                             ModeHighlight();
3703                       }
3704                       StopClocks();
3705                       gameMode = IcsIdle;
3706                       ics_gamenum = -1;
3707                       ics_user_moved = FALSE;
3708                   }
3709                 continue;
3710             }
3711
3712             if (looking_at(buf, &i, "no longer examining game *")) {
3713                 if (gameMode == IcsExamining &&
3714                     atoi(star_match[0]) == ics_gamenum)
3715                   {
3716                       gameMode = IcsIdle;
3717                       ics_gamenum = -1;
3718                       ics_user_moved = FALSE;
3719                   }
3720                 continue;
3721             }
3722
3723             /* Advance leftover_start past any newlines we find,
3724                so only partial lines can get reparsed */
3725             if (looking_at(buf, &i, "\n")) {
3726                 prevColor = curColor;
3727                 if (curColor != ColorNormal) {
3728                     if (oldi > next_out) {
3729                         SendToPlayer(&buf[next_out], oldi - next_out);
3730                         next_out = oldi;
3731                     }
3732                     Colorize(ColorNormal, FALSE);
3733                     curColor = ColorNormal;
3734                 }
3735                 if (started == STARTED_BOARD) {
3736                     started = STARTED_NONE;
3737                     parse[parse_pos] = NULLCHAR;
3738                     ParseBoard12(parse);
3739                     ics_user_moved = 0;
3740
3741                     /* Send premove here */
3742                     if (appData.premove) {
3743                       char str[MSG_SIZ];
3744                       if (currentMove == 0 &&
3745                           gameMode == IcsPlayingWhite &&
3746                           appData.premoveWhite) {
3747                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3748                         if (appData.debugMode)
3749                           fprintf(debugFP, "Sending premove:\n");
3750                         SendToICS(str);
3751                       } else if (currentMove == 1 &&
3752                                  gameMode == IcsPlayingBlack &&
3753                                  appData.premoveBlack) {
3754                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3755                         if (appData.debugMode)
3756                           fprintf(debugFP, "Sending premove:\n");
3757                         SendToICS(str);
3758                       } else if (gotPremove) {
3759                         gotPremove = 0;
3760                         ClearPremoveHighlights();
3761                         if (appData.debugMode)
3762                           fprintf(debugFP, "Sending premove:\n");
3763                           UserMoveEvent(premoveFromX, premoveFromY,
3764                                         premoveToX, premoveToY,
3765                                         premovePromoChar);
3766                       }
3767                     }
3768
3769                     /* Usually suppress following prompt */
3770                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3771                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3772                         if (looking_at(buf, &i, "*% ")) {
3773                             savingComment = FALSE;
3774                             suppressKibitz = 0;
3775                         }
3776                     }
3777                     next_out = i;
3778                 } else if (started == STARTED_HOLDINGS) {
3779                     int gamenum;
3780                     char new_piece[MSG_SIZ];
3781                     started = STARTED_NONE;
3782                     parse[parse_pos] = NULLCHAR;
3783                     if (appData.debugMode)
3784                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3785                                                         parse, currentMove);
3786                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3787                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3788                         if (gameInfo.variant == VariantNormal) {
3789                           /* [HGM] We seem to switch variant during a game!
3790                            * Presumably no holdings were displayed, so we have
3791                            * to move the position two files to the right to
3792                            * create room for them!
3793                            */
3794                           VariantClass newVariant;
3795                           switch(gameInfo.boardWidth) { // base guess on board width
3796                                 case 9:  newVariant = VariantShogi; break;
3797                                 case 10: newVariant = VariantGreat; break;
3798                                 default: newVariant = VariantCrazyhouse; break;
3799                           }
3800                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3801                           /* Get a move list just to see the header, which
3802                              will tell us whether this is really bug or zh */
3803                           if (ics_getting_history == H_FALSE) {
3804                             ics_getting_history = H_REQUESTED;
3805                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3806                             SendToICS(str);
3807                           }
3808                         }
3809                         new_piece[0] = NULLCHAR;
3810                         sscanf(parse, "game %d white [%s black [%s <- %s",
3811                                &gamenum, white_holding, black_holding,
3812                                new_piece);
3813                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3814                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3815                         /* [HGM] copy holdings to board holdings area */
3816                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3817                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3818                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3819 #if ZIPPY
3820                         if (appData.zippyPlay && first.initDone) {
3821                             ZippyHoldings(white_holding, black_holding,
3822                                           new_piece);
3823                         }
3824 #endif /*ZIPPY*/
3825                         if (tinyLayout || smallLayout) {
3826                             char wh[16], bh[16];
3827                             PackHolding(wh, white_holding);
3828                             PackHolding(bh, black_holding);
3829                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3830                                     gameInfo.white, gameInfo.black);
3831                         } else {
3832                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3833                                     gameInfo.white, white_holding,
3834                                     gameInfo.black, black_holding);
3835                         }
3836                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3837                         DrawPosition(FALSE, boards[currentMove]);
3838                         DisplayTitle(str);
3839                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3840                         sscanf(parse, "game %d white [%s black [%s <- %s",
3841                                &gamenum, white_holding, black_holding,
3842                                new_piece);
3843                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3844                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3845                         /* [HGM] copy holdings to partner-board holdings area */
3846                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3847                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3848                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3849                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3850                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3851                       }
3852                     }
3853                     /* Suppress following prompt */
3854                     if (looking_at(buf, &i, "*% ")) {
3855                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3856                         savingComment = FALSE;
3857                         suppressKibitz = 0;
3858                     }
3859                     next_out = i;
3860                 }
3861                 continue;
3862             }
3863
3864             i++;                /* skip unparsed character and loop back */
3865         }
3866
3867         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3868 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3869 //          SendToPlayer(&buf[next_out], i - next_out);
3870             started != STARTED_HOLDINGS && leftover_start > next_out) {
3871             SendToPlayer(&buf[next_out], leftover_start - next_out);
3872             next_out = i;
3873         }
3874
3875         leftover_len = buf_len - leftover_start;
3876         /* if buffer ends with something we couldn't parse,
3877            reparse it after appending the next read */
3878
3879     } else if (count == 0) {
3880         RemoveInputSource(isr);
3881         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3882     } else {
3883         DisplayFatalError(_("Error reading from ICS"), error, 1);
3884     }
3885 }
3886
3887
3888 /* Board style 12 looks like this:
3889
3890    <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
3891
3892  * The "<12> " is stripped before it gets to this routine.  The two
3893  * trailing 0's (flip state and clock ticking) are later addition, and
3894  * some chess servers may not have them, or may have only the first.
3895  * Additional trailing fields may be added in the future.
3896  */
3897
3898 #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"
3899
3900 #define RELATION_OBSERVING_PLAYED    0
3901 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3902 #define RELATION_PLAYING_MYMOVE      1
3903 #define RELATION_PLAYING_NOTMYMOVE  -1
3904 #define RELATION_EXAMINING           2
3905 #define RELATION_ISOLATED_BOARD     -3
3906 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3907
3908 void
3909 ParseBoard12(string)
3910      char *string;
3911 {
3912     GameMode newGameMode;
3913     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3914     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3915     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3916     char to_play, board_chars[200];
3917     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3918     char black[32], white[32];
3919     Board board;
3920     int prevMove = currentMove;
3921     int ticking = 2;
3922     ChessMove moveType;
3923     int fromX, fromY, toX, toY;
3924     char promoChar;
3925     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3926     char *bookHit = NULL; // [HGM] book
3927     Boolean weird = FALSE, reqFlag = FALSE;
3928
3929     fromX = fromY = toX = toY = -1;
3930
3931     newGame = FALSE;
3932
3933     if (appData.debugMode)
3934       fprintf(debugFP, _("Parsing board: %s\n"), string);
3935
3936     move_str[0] = NULLCHAR;
3937     elapsed_time[0] = NULLCHAR;
3938     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3939         int  i = 0, j;
3940         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3941             if(string[i] == ' ') { ranks++; files = 0; }
3942             else files++;
3943             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3944             i++;
3945         }
3946         for(j = 0; j <i; j++) board_chars[j] = string[j];
3947         board_chars[i] = '\0';
3948         string += i + 1;
3949     }
3950     n = sscanf(string, PATTERN, &to_play, &double_push,
3951                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3952                &gamenum, white, black, &relation, &basetime, &increment,
3953                &white_stren, &black_stren, &white_time, &black_time,
3954                &moveNum, str, elapsed_time, move_str, &ics_flip,
3955                &ticking);
3956
3957     if (n < 21) {
3958         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3959         DisplayError(str, 0);
3960         return;
3961     }
3962
3963     /* Convert the move number to internal form */
3964     moveNum = (moveNum - 1) * 2;
3965     if (to_play == 'B') moveNum++;
3966     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3967       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3968                         0, 1);
3969       return;
3970     }
3971
3972     switch (relation) {
3973       case RELATION_OBSERVING_PLAYED:
3974       case RELATION_OBSERVING_STATIC:
3975         if (gamenum == -1) {
3976             /* Old ICC buglet */
3977             relation = RELATION_OBSERVING_STATIC;
3978         }
3979         newGameMode = IcsObserving;
3980         break;
3981       case RELATION_PLAYING_MYMOVE:
3982       case RELATION_PLAYING_NOTMYMOVE:
3983         newGameMode =
3984           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3985             IcsPlayingWhite : IcsPlayingBlack;
3986         break;
3987       case RELATION_EXAMINING:
3988         newGameMode = IcsExamining;
3989         break;
3990       case RELATION_ISOLATED_BOARD:
3991       default:
3992         /* Just display this board.  If user was doing something else,
3993            we will forget about it until the next board comes. */
3994         newGameMode = IcsIdle;
3995         break;
3996       case RELATION_STARTING_POSITION:
3997         newGameMode = gameMode;
3998         break;
3999     }
4000
4001     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4002          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4003       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4004       char *toSqr;
4005       for (k = 0; k < ranks; k++) {
4006         for (j = 0; j < files; j++)
4007           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4008         if(gameInfo.holdingsWidth > 1) {
4009              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4010              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4011         }
4012       }
4013       CopyBoard(partnerBoard, board);
4014       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4015         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4016         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4017       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4018       if(toSqr = strchr(str, '-')) {
4019         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4020         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4021       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4022       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4023       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4024       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4025       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4026       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4027                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4028       DisplayMessage(partnerStatus, "");
4029         partnerBoardValid = TRUE;
4030       return;
4031     }
4032
4033     /* Modify behavior for initial board display on move listing
4034        of wild games.
4035        */
4036     switch (ics_getting_history) {
4037       case H_FALSE:
4038       case H_REQUESTED:
4039         break;
4040       case H_GOT_REQ_HEADER:
4041       case H_GOT_UNREQ_HEADER:
4042         /* This is the initial position of the current game */
4043         gamenum = ics_gamenum;
4044         moveNum = 0;            /* old ICS bug workaround */
4045         if (to_play == 'B') {
4046           startedFromSetupPosition = TRUE;
4047           blackPlaysFirst = TRUE;
4048           moveNum = 1;
4049           if (forwardMostMove == 0) forwardMostMove = 1;
4050           if (backwardMostMove == 0) backwardMostMove = 1;
4051           if (currentMove == 0) currentMove = 1;
4052         }
4053         newGameMode = gameMode;
4054         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4055         break;
4056       case H_GOT_UNWANTED_HEADER:
4057         /* This is an initial board that we don't want */
4058         return;
4059       case H_GETTING_MOVES:
4060         /* Should not happen */
4061         DisplayError(_("Error gathering move list: extra board"), 0);
4062         ics_getting_history = H_FALSE;
4063         return;
4064     }
4065
4066    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4067                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4068      /* [HGM] We seem to have switched variant unexpectedly
4069       * Try to guess new variant from board size
4070       */
4071           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4072           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4073           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4074           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4075           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4076           if(!weird) newVariant = VariantNormal;
4077           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4078           /* Get a move list just to see the header, which
4079              will tell us whether this is really bug or zh */
4080           if (ics_getting_history == H_FALSE) {
4081             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4082             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4083             SendToICS(str);
4084           }
4085     }
4086
4087     /* Take action if this is the first board of a new game, or of a
4088        different game than is currently being displayed.  */
4089     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4090         relation == RELATION_ISOLATED_BOARD) {
4091
4092         /* Forget the old game and get the history (if any) of the new one */
4093         if (gameMode != BeginningOfGame) {
4094           Reset(TRUE, TRUE);
4095         }
4096         newGame = TRUE;
4097         if (appData.autoRaiseBoard) BoardToTop();
4098         prevMove = -3;
4099         if (gamenum == -1) {
4100             newGameMode = IcsIdle;
4101         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4102                    appData.getMoveList && !reqFlag) {
4103             /* Need to get game history */
4104             ics_getting_history = H_REQUESTED;
4105             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4106             SendToICS(str);
4107         }
4108
4109         /* Initially flip the board to have black on the bottom if playing
4110            black or if the ICS flip flag is set, but let the user change
4111            it with the Flip View button. */
4112         flipView = appData.autoFlipView ?
4113           (newGameMode == IcsPlayingBlack) || ics_flip :
4114           appData.flipView;
4115
4116         /* Done with values from previous mode; copy in new ones */
4117         gameMode = newGameMode;
4118         ModeHighlight();
4119         ics_gamenum = gamenum;
4120         if (gamenum == gs_gamenum) {
4121             int klen = strlen(gs_kind);
4122             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4123             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4124             gameInfo.event = StrSave(str);
4125         } else {
4126             gameInfo.event = StrSave("ICS game");
4127         }
4128         gameInfo.site = StrSave(appData.icsHost);
4129         gameInfo.date = PGNDate();
4130         gameInfo.round = StrSave("-");
4131         gameInfo.white = StrSave(white);
4132         gameInfo.black = StrSave(black);
4133         timeControl = basetime * 60 * 1000;
4134         timeControl_2 = 0;
4135         timeIncrement = increment * 1000;
4136         movesPerSession = 0;
4137         gameInfo.timeControl = TimeControlTagValue();
4138         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4139   if (appData.debugMode) {
4140     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4141     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4142     setbuf(debugFP, NULL);
4143   }
4144
4145         gameInfo.outOfBook = NULL;
4146
4147         /* Do we have the ratings? */
4148         if (strcmp(player1Name, white) == 0 &&
4149             strcmp(player2Name, black) == 0) {
4150             if (appData.debugMode)
4151               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4152                       player1Rating, player2Rating);
4153             gameInfo.whiteRating = player1Rating;
4154             gameInfo.blackRating = player2Rating;
4155         } else if (strcmp(player2Name, white) == 0 &&
4156                    strcmp(player1Name, black) == 0) {
4157             if (appData.debugMode)
4158               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4159                       player2Rating, player1Rating);
4160             gameInfo.whiteRating = player2Rating;
4161             gameInfo.blackRating = player1Rating;
4162         }
4163         player1Name[0] = player2Name[0] = NULLCHAR;
4164
4165         /* Silence shouts if requested */
4166         if (appData.quietPlay &&
4167             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4168             SendToICS(ics_prefix);
4169             SendToICS("set shout 0\n");
4170         }
4171     }
4172
4173     /* Deal with midgame name changes */
4174     if (!newGame) {
4175         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4176             if (gameInfo.white) free(gameInfo.white);
4177             gameInfo.white = StrSave(white);
4178         }
4179         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4180             if (gameInfo.black) free(gameInfo.black);
4181             gameInfo.black = StrSave(black);
4182         }
4183     }
4184
4185     /* Throw away game result if anything actually changes in examine mode */
4186     if (gameMode == IcsExamining && !newGame) {
4187         gameInfo.result = GameUnfinished;
4188         if (gameInfo.resultDetails != NULL) {
4189             free(gameInfo.resultDetails);
4190             gameInfo.resultDetails = NULL;
4191         }
4192     }
4193
4194     /* In pausing && IcsExamining mode, we ignore boards coming
4195        in if they are in a different variation than we are. */
4196     if (pauseExamInvalid) return;
4197     if (pausing && gameMode == IcsExamining) {
4198         if (moveNum <= pauseExamForwardMostMove) {
4199             pauseExamInvalid = TRUE;
4200             forwardMostMove = pauseExamForwardMostMove;
4201             return;
4202         }
4203     }
4204
4205   if (appData.debugMode) {
4206     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4207   }
4208     /* Parse the board */
4209     for (k = 0; k < ranks; k++) {
4210       for (j = 0; j < files; j++)
4211         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4212       if(gameInfo.holdingsWidth > 1) {
4213            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4214            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4215       }
4216     }
4217     CopyBoard(boards[moveNum], board);
4218     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4219     if (moveNum == 0) {
4220         startedFromSetupPosition =
4221           !CompareBoards(board, initialPosition);
4222         if(startedFromSetupPosition)
4223             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4224     }
4225
4226     /* [HGM] Set castling rights. Take the outermost Rooks,
4227        to make it also work for FRC opening positions. Note that board12
4228        is really defective for later FRC positions, as it has no way to
4229        indicate which Rook can castle if they are on the same side of King.
4230        For the initial position we grant rights to the outermost Rooks,
4231        and remember thos rights, and we then copy them on positions
4232        later in an FRC game. This means WB might not recognize castlings with
4233        Rooks that have moved back to their original position as illegal,
4234        but in ICS mode that is not its job anyway.
4235     */
4236     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4237     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4238
4239         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4240             if(board[0][i] == WhiteRook) j = i;
4241         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4242         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4243             if(board[0][i] == WhiteRook) j = i;
4244         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4245         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4246             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4247         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4248         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4249             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4250         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4251
4252         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4253         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4254             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4255         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4256             if(board[BOARD_HEIGHT-1][k] == bKing)
4257                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4258         if(gameInfo.variant == VariantTwoKings) {
4259             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4260             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4261             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4262         }
4263     } else { int r;
4264         r = boards[moveNum][CASTLING][0] = initialRights[0];
4265         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4266         r = boards[moveNum][CASTLING][1] = initialRights[1];
4267         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4268         r = boards[moveNum][CASTLING][3] = initialRights[3];
4269         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4270         r = boards[moveNum][CASTLING][4] = initialRights[4];
4271         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4272         /* wildcastle kludge: always assume King has rights */
4273         r = boards[moveNum][CASTLING][2] = initialRights[2];
4274         r = boards[moveNum][CASTLING][5] = initialRights[5];
4275     }
4276     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4277     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4278
4279
4280     if (ics_getting_history == H_GOT_REQ_HEADER ||
4281         ics_getting_history == H_GOT_UNREQ_HEADER) {
4282         /* This was an initial position from a move list, not
4283            the current position */
4284         return;
4285     }
4286
4287     /* Update currentMove and known move number limits */
4288     newMove = newGame || moveNum > forwardMostMove;
4289
4290     if (newGame) {
4291         forwardMostMove = backwardMostMove = currentMove = moveNum;
4292         if (gameMode == IcsExamining && moveNum == 0) {
4293           /* Workaround for ICS limitation: we are not told the wild
4294              type when starting to examine a game.  But if we ask for
4295              the move list, the move list header will tell us */
4296             ics_getting_history = H_REQUESTED;
4297             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4298             SendToICS(str);
4299         }
4300     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4301                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4302 #if ZIPPY
4303         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4304         /* [HGM] applied this also to an engine that is silently watching        */
4305         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4306             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4307             gameInfo.variant == currentlyInitializedVariant) {
4308           takeback = forwardMostMove - moveNum;
4309           for (i = 0; i < takeback; i++) {
4310             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4311             SendToProgram("undo\n", &first);
4312           }
4313         }
4314 #endif
4315
4316         forwardMostMove = moveNum;
4317         if (!pausing || currentMove > forwardMostMove)
4318           currentMove = forwardMostMove;
4319     } else {
4320         /* New part of history that is not contiguous with old part */
4321         if (pausing && gameMode == IcsExamining) {
4322             pauseExamInvalid = TRUE;
4323             forwardMostMove = pauseExamForwardMostMove;
4324             return;
4325         }
4326         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4327 #if ZIPPY
4328             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4329                 // [HGM] when we will receive the move list we now request, it will be
4330                 // fed to the engine from the first move on. So if the engine is not
4331                 // in the initial position now, bring it there.
4332                 InitChessProgram(&first, 0);
4333             }
4334 #endif
4335             ics_getting_history = H_REQUESTED;
4336             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4337             SendToICS(str);
4338         }
4339         forwardMostMove = backwardMostMove = currentMove = moveNum;
4340     }
4341
4342     /* Update the clocks */
4343     if (strchr(elapsed_time, '.')) {
4344       /* Time is in ms */
4345       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4346       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4347     } else {
4348       /* Time is in seconds */
4349       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4350       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4351     }
4352
4353
4354 #if ZIPPY
4355     if (appData.zippyPlay && newGame &&
4356         gameMode != IcsObserving && gameMode != IcsIdle &&
4357         gameMode != IcsExamining)
4358       ZippyFirstBoard(moveNum, basetime, increment);
4359 #endif
4360
4361     /* Put the move on the move list, first converting
4362        to canonical algebraic form. */
4363     if (moveNum > 0) {
4364   if (appData.debugMode) {
4365     if (appData.debugMode) { int f = forwardMostMove;
4366         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4367                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4368                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4369     }
4370     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4371     fprintf(debugFP, "moveNum = %d\n", moveNum);
4372     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4373     setbuf(debugFP, NULL);
4374   }
4375         if (moveNum <= backwardMostMove) {
4376             /* We don't know what the board looked like before
4377                this move.  Punt. */
4378           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4379             strcat(parseList[moveNum - 1], " ");
4380             strcat(parseList[moveNum - 1], elapsed_time);
4381             moveList[moveNum - 1][0] = NULLCHAR;
4382         } else if (strcmp(move_str, "none") == 0) {
4383             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4384             /* Again, we don't know what the board looked like;
4385                this is really the start of the game. */
4386             parseList[moveNum - 1][0] = NULLCHAR;
4387             moveList[moveNum - 1][0] = NULLCHAR;
4388             backwardMostMove = moveNum;
4389             startedFromSetupPosition = TRUE;
4390             fromX = fromY = toX = toY = -1;
4391         } else {
4392           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4393           //                 So we parse the long-algebraic move string in stead of the SAN move
4394           int valid; char buf[MSG_SIZ], *prom;
4395
4396           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4397                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4398           // str looks something like "Q/a1-a2"; kill the slash
4399           if(str[1] == '/')
4400             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4401           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4402           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4403                 strcat(buf, prom); // long move lacks promo specification!
4404           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4405                 if(appData.debugMode)
4406                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4407                 safeStrCpy(move_str, buf, MSG_SIZ);
4408           }
4409           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4410                                 &fromX, &fromY, &toX, &toY, &promoChar)
4411                || ParseOneMove(buf, moveNum - 1, &moveType,
4412                                 &fromX, &fromY, &toX, &toY, &promoChar);
4413           // end of long SAN patch
4414           if (valid) {
4415             (void) CoordsToAlgebraic(boards[moveNum - 1],
4416                                      PosFlags(moveNum - 1),
4417                                      fromY, fromX, toY, toX, promoChar,
4418                                      parseList[moveNum-1]);
4419             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4420               case MT_NONE:
4421               case MT_STALEMATE:
4422               default:
4423                 break;
4424               case MT_CHECK:
4425                 if(gameInfo.variant != VariantShogi)
4426                     strcat(parseList[moveNum - 1], "+");
4427                 break;
4428               case MT_CHECKMATE:
4429               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4430                 strcat(parseList[moveNum - 1], "#");
4431                 break;
4432             }
4433             strcat(parseList[moveNum - 1], " ");
4434             strcat(parseList[moveNum - 1], elapsed_time);
4435             /* currentMoveString is set as a side-effect of ParseOneMove */
4436             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4437             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4438             strcat(moveList[moveNum - 1], "\n");
4439
4440             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4441                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4442               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4443                 ChessSquare old, new = boards[moveNum][k][j];
4444                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4445                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4446                   if(old == new) continue;
4447                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4448                   else if(new == WhiteWazir || new == BlackWazir) {
4449                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4450                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4451                       else boards[moveNum][k][j] = old; // preserve type of Gold
4452                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4453                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4454               }
4455           } else {
4456             /* Move from ICS was illegal!?  Punt. */
4457             if (appData.debugMode) {
4458               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4459               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4460             }
4461             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4462             strcat(parseList[moveNum - 1], " ");
4463             strcat(parseList[moveNum - 1], elapsed_time);
4464             moveList[moveNum - 1][0] = NULLCHAR;
4465             fromX = fromY = toX = toY = -1;
4466           }
4467         }
4468   if (appData.debugMode) {
4469     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4470     setbuf(debugFP, NULL);
4471   }
4472
4473 #if ZIPPY
4474         /* Send move to chess program (BEFORE animating it). */
4475         if (appData.zippyPlay && !newGame && newMove &&
4476            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4477
4478             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4479                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4480                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4481                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4482                             move_str);
4483                     DisplayError(str, 0);
4484                 } else {
4485                     if (first.sendTime) {
4486                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4487                     }
4488                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4489                     if (firstMove && !bookHit) {
4490                         firstMove = FALSE;
4491                         if (first.useColors) {
4492                           SendToProgram(gameMode == IcsPlayingWhite ?
4493                                         "white\ngo\n" :
4494                                         "black\ngo\n", &first);
4495                         } else {
4496                           SendToProgram("go\n", &first);
4497                         }
4498                         first.maybeThinking = TRUE;
4499                     }
4500                 }
4501             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4502               if (moveList[moveNum - 1][0] == NULLCHAR) {
4503                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4504                 DisplayError(str, 0);
4505               } else {
4506                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4507                 SendMoveToProgram(moveNum - 1, &first);
4508               }
4509             }
4510         }
4511 #endif
4512     }
4513
4514     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4515         /* If move comes from a remote source, animate it.  If it
4516            isn't remote, it will have already been animated. */
4517         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4518             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4519         }
4520         if (!pausing && appData.highlightLastMove) {
4521             SetHighlights(fromX, fromY, toX, toY);
4522         }
4523     }
4524
4525     /* Start the clocks */
4526     whiteFlag = blackFlag = FALSE;
4527     appData.clockMode = !(basetime == 0 && increment == 0);
4528     if (ticking == 0) {
4529       ics_clock_paused = TRUE;
4530       StopClocks();
4531     } else if (ticking == 1) {
4532       ics_clock_paused = FALSE;
4533     }
4534     if (gameMode == IcsIdle ||
4535         relation == RELATION_OBSERVING_STATIC ||
4536         relation == RELATION_EXAMINING ||
4537         ics_clock_paused)
4538       DisplayBothClocks();
4539     else
4540       StartClocks();
4541
4542     /* Display opponents and material strengths */
4543     if (gameInfo.variant != VariantBughouse &&
4544         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4545         if (tinyLayout || smallLayout) {
4546             if(gameInfo.variant == VariantNormal)
4547               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4548                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4549                     basetime, increment);
4550             else
4551               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4552                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4553                     basetime, increment, (int) gameInfo.variant);
4554         } else {
4555             if(gameInfo.variant == VariantNormal)
4556               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4557                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4558                     basetime, increment);
4559             else
4560               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4561                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4562                     basetime, increment, VariantName(gameInfo.variant));
4563         }
4564         DisplayTitle(str);
4565   if (appData.debugMode) {
4566     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4567   }
4568     }
4569
4570
4571     /* Display the board */
4572     if (!pausing && !appData.noGUI) {
4573
4574       if (appData.premove)
4575           if (!gotPremove ||
4576              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4577              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4578               ClearPremoveHighlights();
4579
4580       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4581         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4582       DrawPosition(j, boards[currentMove]);
4583
4584       DisplayMove(moveNum - 1);
4585       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4586             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4587               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4588         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4589       }
4590     }
4591
4592     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4593 #if ZIPPY
4594     if(bookHit) { // [HGM] book: simulate book reply
4595         static char bookMove[MSG_SIZ]; // a bit generous?
4596
4597         programStats.nodes = programStats.depth = programStats.time =
4598         programStats.score = programStats.got_only_move = 0;
4599         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4600
4601         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4602         strcat(bookMove, bookHit);
4603         HandleMachineMove(bookMove, &first);
4604     }
4605 #endif
4606 }
4607
4608 void
4609 GetMoveListEvent()
4610 {
4611     char buf[MSG_SIZ];
4612     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4613         ics_getting_history = H_REQUESTED;
4614         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4615         SendToICS(buf);
4616     }
4617 }
4618
4619 void
4620 AnalysisPeriodicEvent(force)
4621      int force;
4622 {
4623     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4624          && !force) || !appData.periodicUpdates)
4625       return;
4626
4627     /* Send . command to Crafty to collect stats */
4628     SendToProgram(".\n", &first);
4629
4630     /* Don't send another until we get a response (this makes
4631        us stop sending to old Crafty's which don't understand
4632        the "." command (sending illegal cmds resets node count & time,
4633        which looks bad)) */
4634     programStats.ok_to_send = 0;
4635 }
4636
4637 void ics_update_width(new_width)
4638         int new_width;
4639 {
4640         ics_printf("set width %d\n", new_width);
4641 }
4642
4643 void
4644 SendMoveToProgram(moveNum, cps)
4645      int moveNum;
4646      ChessProgramState *cps;
4647 {
4648     char buf[MSG_SIZ];
4649
4650     if (cps->useUsermove) {
4651       SendToProgram("usermove ", cps);
4652     }
4653     if (cps->useSAN) {
4654       char *space;
4655       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4656         int len = space - parseList[moveNum];
4657         memcpy(buf, parseList[moveNum], len);
4658         buf[len++] = '\n';
4659         buf[len] = NULLCHAR;
4660       } else {
4661         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4662       }
4663       SendToProgram(buf, cps);
4664     } else {
4665       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4666         AlphaRank(moveList[moveNum], 4);
4667         SendToProgram(moveList[moveNum], cps);
4668         AlphaRank(moveList[moveNum], 4); // and back
4669       } else
4670       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4671        * the engine. It would be nice to have a better way to identify castle
4672        * moves here. */
4673       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4674                                                                          && cps->useOOCastle) {
4675         int fromX = moveList[moveNum][0] - AAA;
4676         int fromY = moveList[moveNum][1] - ONE;
4677         int toX = moveList[moveNum][2] - AAA;
4678         int toY = moveList[moveNum][3] - ONE;
4679         if((boards[moveNum][fromY][fromX] == WhiteKing
4680             && boards[moveNum][toY][toX] == WhiteRook)
4681            || (boards[moveNum][fromY][fromX] == BlackKing
4682                && boards[moveNum][toY][toX] == BlackRook)) {
4683           if(toX > fromX) SendToProgram("O-O\n", cps);
4684           else SendToProgram("O-O-O\n", cps);
4685         }
4686         else SendToProgram(moveList[moveNum], cps);
4687       }
4688       else SendToProgram(moveList[moveNum], cps);
4689       /* End of additions by Tord */
4690     }
4691
4692     /* [HGM] setting up the opening has brought engine in force mode! */
4693     /*       Send 'go' if we are in a mode where machine should play. */
4694     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4695         (gameMode == TwoMachinesPlay   ||
4696 #if ZIPPY
4697          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4698 #endif
4699          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4700         SendToProgram("go\n", cps);
4701   if (appData.debugMode) {
4702     fprintf(debugFP, "(extra)\n");
4703   }
4704     }
4705     setboardSpoiledMachineBlack = 0;
4706 }
4707
4708 void
4709 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4710      ChessMove moveType;
4711      int fromX, fromY, toX, toY;
4712      char promoChar;
4713 {
4714     char user_move[MSG_SIZ];
4715
4716     switch (moveType) {
4717       default:
4718         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4719                 (int)moveType, fromX, fromY, toX, toY);
4720         DisplayError(user_move + strlen("say "), 0);
4721         break;
4722       case WhiteKingSideCastle:
4723       case BlackKingSideCastle:
4724       case WhiteQueenSideCastleWild:
4725       case BlackQueenSideCastleWild:
4726       /* PUSH Fabien */
4727       case WhiteHSideCastleFR:
4728       case BlackHSideCastleFR:
4729       /* POP Fabien */
4730         snprintf(user_move, MSG_SIZ, "o-o\n");
4731         break;
4732       case WhiteQueenSideCastle:
4733       case BlackQueenSideCastle:
4734       case WhiteKingSideCastleWild:
4735       case BlackKingSideCastleWild:
4736       /* PUSH Fabien */
4737       case WhiteASideCastleFR:
4738       case BlackASideCastleFR:
4739       /* POP Fabien */
4740         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4741         break;
4742       case WhiteNonPromotion:
4743       case BlackNonPromotion:
4744         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4745         break;
4746       case WhitePromotion:
4747       case BlackPromotion:
4748         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4749           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4750                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4751                 PieceToChar(WhiteFerz));
4752         else if(gameInfo.variant == VariantGreat)
4753           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4754                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4755                 PieceToChar(WhiteMan));
4756         else
4757           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4758                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4759                 promoChar);
4760         break;
4761       case WhiteDrop:
4762       case BlackDrop:
4763       drop:
4764         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4765                  ToUpper(PieceToChar((ChessSquare) fromX)),
4766                  AAA + toX, ONE + toY);
4767         break;
4768       case IllegalMove:  /* could be a variant we don't quite understand */
4769         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4770       case NormalMove:
4771       case WhiteCapturesEnPassant:
4772       case BlackCapturesEnPassant:
4773         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4774                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4775         break;
4776     }
4777     SendToICS(user_move);
4778     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4779         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4780 }
4781
4782 void
4783 UploadGameEvent()
4784 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4785     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4786     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4787     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4788         DisplayError("You cannot do this while you are playing or observing", 0);
4789         return;
4790     }
4791     if(gameMode != IcsExamining) { // is this ever not the case?
4792         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4793
4794         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4795           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4796         } else { // on FICS we must first go to general examine mode
4797           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4798         }
4799         if(gameInfo.variant != VariantNormal) {
4800             // try figure out wild number, as xboard names are not always valid on ICS
4801             for(i=1; i<=36; i++) {
4802               snprintf(buf, MSG_SIZ, "wild/%d", i);
4803                 if(StringToVariant(buf) == gameInfo.variant) break;
4804             }
4805             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4806             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4807             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4808         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4809         SendToICS(ics_prefix);
4810         SendToICS(buf);
4811         if(startedFromSetupPosition || backwardMostMove != 0) {
4812           fen = PositionToFEN(backwardMostMove, NULL);
4813           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4814             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4815             SendToICS(buf);
4816           } else { // FICS: everything has to set by separate bsetup commands
4817             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4818             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4819             SendToICS(buf);
4820             if(!WhiteOnMove(backwardMostMove)) {
4821                 SendToICS("bsetup tomove black\n");
4822             }
4823             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4824             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4825             SendToICS(buf);
4826             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4827             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4828             SendToICS(buf);
4829             i = boards[backwardMostMove][EP_STATUS];
4830             if(i >= 0) { // set e.p.
4831               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4832                 SendToICS(buf);
4833             }
4834             bsetup++;
4835           }
4836         }
4837       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4838             SendToICS("bsetup done\n"); // switch to normal examining.
4839     }
4840     for(i = backwardMostMove; i<last; i++) {
4841         char buf[20];
4842         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4843         SendToICS(buf);
4844     }
4845     SendToICS(ics_prefix);
4846     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4847 }
4848
4849 void
4850 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4851      int rf, ff, rt, ft;
4852      char promoChar;
4853      char move[7];
4854 {
4855     if (rf == DROP_RANK) {
4856       sprintf(move, "%c@%c%c\n",
4857                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4858     } else {
4859         if (promoChar == 'x' || promoChar == NULLCHAR) {
4860           sprintf(move, "%c%c%c%c\n",
4861                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4862         } else {
4863             sprintf(move, "%c%c%c%c%c\n",
4864                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4865         }
4866     }
4867 }
4868
4869 void
4870 ProcessICSInitScript(f)
4871      FILE *f;
4872 {
4873     char buf[MSG_SIZ];
4874
4875     while (fgets(buf, MSG_SIZ, f)) {
4876         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4877     }
4878
4879     fclose(f);
4880 }
4881
4882
4883 static int lastX, lastY, selectFlag, dragging;
4884
4885 void
4886 Sweep(int step)
4887 {
4888     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4889     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4890     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4891     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4892     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4893     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4894     do {
4895         promoSweep -= step;
4896         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4897         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4898         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4899         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4900         if(!step) step = 1;
4901     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4902             appData.testLegality && (promoSweep == king ||
4903             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4904     ChangeDragPiece(promoSweep);
4905 }
4906
4907 int PromoScroll(int x, int y)
4908 {
4909   int step = 0;
4910
4911   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4912   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4913   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4914   if(!step) return FALSE;
4915   lastX = x; lastY = y;
4916   if((promoSweep < BlackPawn) == flipView) step = -step;
4917   if(step > 0) selectFlag = 1;
4918   if(!selectFlag) Sweep(step);
4919   return FALSE;
4920 }
4921
4922 void
4923 NextPiece(int step)
4924 {
4925     ChessSquare piece = boards[currentMove][toY][toX];
4926     do {
4927         pieceSweep -= step;
4928         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4929         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4930         if(!step) step = -1;
4931     } while(PieceToChar(pieceSweep) == '.');
4932     boards[currentMove][toY][toX] = pieceSweep;
4933     DrawPosition(FALSE, boards[currentMove]);
4934     boards[currentMove][toY][toX] = piece;
4935 }
4936 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4937 void
4938 AlphaRank(char *move, int n)
4939 {
4940 //    char *p = move, c; int x, y;
4941
4942     if (appData.debugMode) {
4943         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4944     }
4945
4946     if(move[1]=='*' &&
4947        move[2]>='0' && move[2]<='9' &&
4948        move[3]>='a' && move[3]<='x'    ) {
4949         move[1] = '@';
4950         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4951         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4952     } else
4953     if(move[0]>='0' && move[0]<='9' &&
4954        move[1]>='a' && move[1]<='x' &&
4955        move[2]>='0' && move[2]<='9' &&
4956        move[3]>='a' && move[3]<='x'    ) {
4957         /* input move, Shogi -> normal */
4958         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4959         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4960         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4961         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4962     } else
4963     if(move[1]=='@' &&
4964        move[3]>='0' && move[3]<='9' &&
4965        move[2]>='a' && move[2]<='x'    ) {
4966         move[1] = '*';
4967         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4968         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4969     } else
4970     if(
4971        move[0]>='a' && move[0]<='x' &&
4972        move[3]>='0' && move[3]<='9' &&
4973        move[2]>='a' && move[2]<='x'    ) {
4974          /* output move, normal -> Shogi */
4975         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4976         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4977         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4978         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4979         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4980     }
4981     if (appData.debugMode) {
4982         fprintf(debugFP, "   out = '%s'\n", move);
4983     }
4984 }
4985
4986 char yy_textstr[8000];
4987
4988 /* Parser for moves from gnuchess, ICS, or user typein box */
4989 Boolean
4990 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4991      char *move;
4992      int moveNum;
4993      ChessMove *moveType;
4994      int *fromX, *fromY, *toX, *toY;
4995      char *promoChar;
4996 {
4997     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4998
4999     switch (*moveType) {
5000       case WhitePromotion:
5001       case BlackPromotion:
5002       case WhiteNonPromotion:
5003       case BlackNonPromotion:
5004       case NormalMove:
5005       case WhiteCapturesEnPassant:
5006       case BlackCapturesEnPassant:
5007       case WhiteKingSideCastle:
5008       case WhiteQueenSideCastle:
5009       case BlackKingSideCastle:
5010       case BlackQueenSideCastle:
5011       case WhiteKingSideCastleWild:
5012       case WhiteQueenSideCastleWild:
5013       case BlackKingSideCastleWild:
5014       case BlackQueenSideCastleWild:
5015       /* Code added by Tord: */
5016       case WhiteHSideCastleFR:
5017       case WhiteASideCastleFR:
5018       case BlackHSideCastleFR:
5019       case BlackASideCastleFR:
5020       /* End of code added by Tord */
5021       case IllegalMove:         /* bug or odd chess variant */
5022         *fromX = currentMoveString[0] - AAA;
5023         *fromY = currentMoveString[1] - ONE;
5024         *toX = currentMoveString[2] - AAA;
5025         *toY = currentMoveString[3] - ONE;
5026         *promoChar = currentMoveString[4];
5027         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5028             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5029     if (appData.debugMode) {
5030         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5031     }
5032             *fromX = *fromY = *toX = *toY = 0;
5033             return FALSE;
5034         }
5035         if (appData.testLegality) {
5036           return (*moveType != IllegalMove);
5037         } else {
5038           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5039                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5040         }
5041
5042       case WhiteDrop:
5043       case BlackDrop:
5044         *fromX = *moveType == WhiteDrop ?
5045           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5046           (int) CharToPiece(ToLower(currentMoveString[0]));
5047         *fromY = DROP_RANK;
5048         *toX = currentMoveString[2] - AAA;
5049         *toY = currentMoveString[3] - ONE;
5050         *promoChar = NULLCHAR;
5051         return TRUE;
5052
5053       case AmbiguousMove:
5054       case ImpossibleMove:
5055       case EndOfFile:
5056       case ElapsedTime:
5057       case Comment:
5058       case PGNTag:
5059       case NAG:
5060       case WhiteWins:
5061       case BlackWins:
5062       case GameIsDrawn:
5063       default:
5064     if (appData.debugMode) {
5065         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5066     }
5067         /* bug? */
5068         *fromX = *fromY = *toX = *toY = 0;
5069         *promoChar = NULLCHAR;
5070         return FALSE;
5071     }
5072 }
5073
5074
5075 void
5076 ParsePV(char *pv, Boolean storeComments)
5077 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5078   int fromX, fromY, toX, toY; char promoChar;
5079   ChessMove moveType;
5080   Boolean valid;
5081   int nr = 0;
5082
5083   endPV = forwardMostMove;
5084   do {
5085     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5086     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5087     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5088 if(appData.debugMode){
5089 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);
5090 }
5091     if(!valid && nr == 0 &&
5092        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5093         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5094         // Hande case where played move is different from leading PV move
5095         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5096         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5097         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5098         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5099           endPV += 2; // if position different, keep this
5100           moveList[endPV-1][0] = fromX + AAA;
5101           moveList[endPV-1][1] = fromY + ONE;
5102           moveList[endPV-1][2] = toX + AAA;
5103           moveList[endPV-1][3] = toY + ONE;
5104           parseList[endPV-1][0] = NULLCHAR;
5105           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5106         }
5107       }
5108     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5109     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5110     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5111     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5112         valid++; // allow comments in PV
5113         continue;
5114     }
5115     nr++;
5116     if(endPV+1 > framePtr) break; // no space, truncate
5117     if(!valid) break;
5118     endPV++;
5119     CopyBoard(boards[endPV], boards[endPV-1]);
5120     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5121     moveList[endPV-1][0] = fromX + AAA;
5122     moveList[endPV-1][1] = fromY + ONE;
5123     moveList[endPV-1][2] = toX + AAA;
5124     moveList[endPV-1][3] = toY + ONE;
5125     moveList[endPV-1][4] = promoChar;
5126     moveList[endPV-1][5] = NULLCHAR;
5127     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5128     if(storeComments)
5129         CoordsToAlgebraic(boards[endPV - 1],
5130                              PosFlags(endPV - 1),
5131                              fromY, fromX, toY, toX, promoChar,
5132                              parseList[endPV - 1]);
5133     else
5134         parseList[endPV-1][0] = NULLCHAR;
5135   } while(valid);
5136   currentMove = endPV;
5137   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5138   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5139                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5140   DrawPosition(TRUE, boards[currentMove]);
5141 }
5142
5143 Boolean
5144 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5145 {
5146         int startPV;
5147         char *p;
5148
5149         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5150         lastX = x; lastY = y;
5151         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5152         startPV = index;
5153         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5154         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5155         index = startPV;
5156         do{ while(buf[index] && buf[index] != '\n') index++;
5157         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5158         buf[index] = 0;
5159         ParsePV(buf+startPV, FALSE);
5160         *start = startPV; *end = index-1;
5161         return TRUE;
5162 }
5163
5164 Boolean
5165 LoadPV(int x, int y)
5166 { // called on right mouse click to load PV
5167   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5168   lastX = x; lastY = y;
5169   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5170   return TRUE;
5171 }
5172
5173 void
5174 UnLoadPV()
5175 {
5176   if(endPV < 0) return;
5177   endPV = -1;
5178   currentMove = forwardMostMove;
5179   ClearPremoveHighlights();
5180   DrawPosition(TRUE, boards[currentMove]);
5181 }
5182
5183 void
5184 MovePV(int x, int y, int h)
5185 { // step through PV based on mouse coordinates (called on mouse move)
5186   int margin = h>>3, step = 0;
5187
5188   // we must somehow check if right button is still down (might be released off board!)
5189   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5190   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5191   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5192   if(!step) return;
5193   lastX = x; lastY = y;
5194
5195   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5196   if(endPV < 0) return;
5197   if(y < margin) step = 1; else
5198   if(y > h - margin) step = -1;
5199   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5200   currentMove += step;
5201   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5202   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5203                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5204   DrawPosition(FALSE, boards[currentMove]);
5205 }
5206
5207
5208 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5209 // All positions will have equal probability, but the current method will not provide a unique
5210 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5211 #define DARK 1
5212 #define LITE 2
5213 #define ANY 3
5214
5215 int squaresLeft[4];
5216 int piecesLeft[(int)BlackPawn];
5217 int seed, nrOfShuffles;
5218
5219 void GetPositionNumber()
5220 {       // sets global variable seed
5221         int i;
5222
5223         seed = appData.defaultFrcPosition;
5224         if(seed < 0) { // randomize based on time for negative FRC position numbers
5225                 for(i=0; i<50; i++) seed += random();
5226                 seed = random() ^ random() >> 8 ^ random() << 8;
5227                 if(seed<0) seed = -seed;
5228         }
5229 }
5230
5231 int put(Board board, int pieceType, int rank, int n, int shade)
5232 // put the piece on the (n-1)-th empty squares of the given shade
5233 {
5234         int i;
5235
5236         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5237                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5238                         board[rank][i] = (ChessSquare) pieceType;
5239                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5240                         squaresLeft[ANY]--;
5241                         piecesLeft[pieceType]--;
5242                         return i;
5243                 }
5244         }
5245         return -1;
5246 }
5247
5248
5249 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5250 // calculate where the next piece goes, (any empty square), and put it there
5251 {
5252         int i;
5253
5254         i = seed % squaresLeft[shade];
5255         nrOfShuffles *= squaresLeft[shade];
5256         seed /= squaresLeft[shade];
5257         put(board, pieceType, rank, i, shade);
5258 }
5259
5260 void AddTwoPieces(Board board, int pieceType, int rank)
5261 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5262 {
5263         int i, n=squaresLeft[ANY], j=n-1, k;
5264
5265         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5266         i = seed % k;  // pick one
5267         nrOfShuffles *= k;
5268         seed /= k;
5269         while(i >= j) i -= j--;
5270         j = n - 1 - j; i += j;
5271         put(board, pieceType, rank, j, ANY);
5272         put(board, pieceType, rank, i, ANY);
5273 }
5274
5275 void SetUpShuffle(Board board, int number)
5276 {
5277         int i, p, first=1;
5278
5279         GetPositionNumber(); nrOfShuffles = 1;
5280
5281         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5282         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5283         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5284
5285         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5286
5287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5288             p = (int) board[0][i];
5289             if(p < (int) BlackPawn) piecesLeft[p] ++;
5290             board[0][i] = EmptySquare;
5291         }
5292
5293         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5294             // shuffles restricted to allow normal castling put KRR first
5295             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5296                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5297             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5298                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5299             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5300                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5301             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5302                 put(board, WhiteRook, 0, 0, ANY);
5303             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5304         }
5305
5306         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5307             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5308             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5309                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5310                 while(piecesLeft[p] >= 2) {
5311                     AddOnePiece(board, p, 0, LITE);
5312                     AddOnePiece(board, p, 0, DARK);
5313                 }
5314                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5315             }
5316
5317         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5318             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5319             // but we leave King and Rooks for last, to possibly obey FRC restriction
5320             if(p == (int)WhiteRook) continue;
5321             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5322             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5323         }
5324
5325         // now everything is placed, except perhaps King (Unicorn) and Rooks
5326
5327         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5328             // Last King gets castling rights
5329             while(piecesLeft[(int)WhiteUnicorn]) {
5330                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5331                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5332             }
5333
5334             while(piecesLeft[(int)WhiteKing]) {
5335                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5336                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5337             }
5338
5339
5340         } else {
5341             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5342             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5343         }
5344
5345         // Only Rooks can be left; simply place them all
5346         while(piecesLeft[(int)WhiteRook]) {
5347                 i = put(board, WhiteRook, 0, 0, ANY);
5348                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5349                         if(first) {
5350                                 first=0;
5351                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5352                         }
5353                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5354                 }
5355         }
5356         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5357             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5358         }
5359
5360         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5361 }
5362
5363 int SetCharTable( char *table, const char * map )
5364 /* [HGM] moved here from winboard.c because of its general usefulness */
5365 /*       Basically a safe strcpy that uses the last character as King */
5366 {
5367     int result = FALSE; int NrPieces;
5368
5369     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5370                     && NrPieces >= 12 && !(NrPieces&1)) {
5371         int i; /* [HGM] Accept even length from 12 to 34 */
5372
5373         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5374         for( i=0; i<NrPieces/2-1; i++ ) {
5375             table[i] = map[i];
5376             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5377         }
5378         table[(int) WhiteKing]  = map[NrPieces/2-1];
5379         table[(int) BlackKing]  = map[NrPieces-1];
5380
5381         result = TRUE;
5382     }
5383
5384     return result;
5385 }
5386
5387 void Prelude(Board board)
5388 {       // [HGM] superchess: random selection of exo-pieces
5389         int i, j, k; ChessSquare p;
5390         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5391
5392         GetPositionNumber(); // use FRC position number
5393
5394         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5395             SetCharTable(pieceToChar, appData.pieceToCharTable);
5396             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5397                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5398         }
5399
5400         j = seed%4;                 seed /= 4;
5401         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5402         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5403         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5404         j = seed%3 + (seed%3 >= j); seed /= 3;
5405         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5406         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5407         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5408         j = seed%3;                 seed /= 3;
5409         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5410         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5411         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5412         j = seed%2 + (seed%2 >= j); seed /= 2;
5413         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5414         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5415         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5416         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5417         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5418         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5419         put(board, exoPieces[0],    0, 0, ANY);
5420         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5421 }
5422
5423 void
5424 InitPosition(redraw)
5425      int redraw;
5426 {
5427     ChessSquare (* pieces)[BOARD_FILES];
5428     int i, j, pawnRow, overrule,
5429     oldx = gameInfo.boardWidth,
5430     oldy = gameInfo.boardHeight,
5431     oldh = gameInfo.holdingsWidth;
5432     static int oldv;
5433
5434     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5435
5436     /* [AS] Initialize pv info list [HGM] and game status */
5437     {
5438         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5439             pvInfoList[i].depth = 0;
5440             boards[i][EP_STATUS] = EP_NONE;
5441             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5442         }
5443
5444         initialRulePlies = 0; /* 50-move counter start */
5445
5446         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5447         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5448     }
5449
5450
5451     /* [HGM] logic here is completely changed. In stead of full positions */
5452     /* the initialized data only consist of the two backranks. The switch */
5453     /* selects which one we will use, which is than copied to the Board   */
5454     /* initialPosition, which for the rest is initialized by Pawns and    */
5455     /* empty squares. This initial position is then copied to boards[0],  */
5456     /* possibly after shuffling, so that it remains available.            */
5457
5458     gameInfo.holdingsWidth = 0; /* default board sizes */
5459     gameInfo.boardWidth    = 8;
5460     gameInfo.boardHeight   = 8;
5461     gameInfo.holdingsSize  = 0;
5462     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5463     for(i=0; i<BOARD_FILES-2; i++)
5464       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5465     initialPosition[EP_STATUS] = EP_NONE;
5466     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5467     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5468          SetCharTable(pieceNickName, appData.pieceNickNames);
5469     else SetCharTable(pieceNickName, "............");
5470     pieces = FIDEArray;
5471
5472     switch (gameInfo.variant) {
5473     case VariantFischeRandom:
5474       shuffleOpenings = TRUE;
5475     default:
5476       break;
5477     case VariantShatranj:
5478       pieces = ShatranjArray;
5479       nrCastlingRights = 0;
5480       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5481       break;
5482     case VariantMakruk:
5483       pieces = makrukArray;
5484       nrCastlingRights = 0;
5485       startedFromSetupPosition = TRUE;
5486       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5487       break;
5488     case VariantTwoKings:
5489       pieces = twoKingsArray;
5490       break;
5491     case VariantCapaRandom:
5492       shuffleOpenings = TRUE;
5493     case VariantCapablanca:
5494       pieces = CapablancaArray;
5495       gameInfo.boardWidth = 10;
5496       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5497       break;
5498     case VariantGothic:
5499       pieces = GothicArray;
5500       gameInfo.boardWidth = 10;
5501       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5502       break;
5503     case VariantSChess:
5504       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5505       gameInfo.holdingsSize = 7;
5506       break;
5507     case VariantJanus:
5508       pieces = JanusArray;
5509       gameInfo.boardWidth = 10;
5510       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5511       nrCastlingRights = 6;
5512         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5513         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5514         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5515         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5516         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5517         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5518       break;
5519     case VariantFalcon:
5520       pieces = FalconArray;
5521       gameInfo.boardWidth = 10;
5522       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5523       break;
5524     case VariantXiangqi:
5525       pieces = XiangqiArray;
5526       gameInfo.boardWidth  = 9;
5527       gameInfo.boardHeight = 10;
5528       nrCastlingRights = 0;
5529       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5530       break;
5531     case VariantShogi:
5532       pieces = ShogiArray;
5533       gameInfo.boardWidth  = 9;
5534       gameInfo.boardHeight = 9;
5535       gameInfo.holdingsSize = 7;
5536       nrCastlingRights = 0;
5537       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5538       break;
5539     case VariantCourier:
5540       pieces = CourierArray;
5541       gameInfo.boardWidth  = 12;
5542       nrCastlingRights = 0;
5543       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5544       break;
5545     case VariantKnightmate:
5546       pieces = KnightmateArray;
5547       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5548       break;
5549     case VariantSpartan:
5550       pieces = SpartanArray;
5551       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5552       break;
5553     case VariantFairy:
5554       pieces = fairyArray;
5555       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5556       break;
5557     case VariantGreat:
5558       pieces = GreatArray;
5559       gameInfo.boardWidth = 10;
5560       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5561       gameInfo.holdingsSize = 8;
5562       break;
5563     case VariantSuper:
5564       pieces = FIDEArray;
5565       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5566       gameInfo.holdingsSize = 8;
5567       startedFromSetupPosition = TRUE;
5568       break;
5569     case VariantCrazyhouse:
5570     case VariantBughouse:
5571       pieces = FIDEArray;
5572       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5573       gameInfo.holdingsSize = 5;
5574       break;
5575     case VariantWildCastle:
5576       pieces = FIDEArray;
5577       /* !!?shuffle with kings guaranteed to be on d or e file */
5578       shuffleOpenings = 1;
5579       break;
5580     case VariantNoCastle:
5581       pieces = FIDEArray;
5582       nrCastlingRights = 0;
5583       /* !!?unconstrained back-rank shuffle */
5584       shuffleOpenings = 1;
5585       break;
5586     }
5587
5588     overrule = 0;
5589     if(appData.NrFiles >= 0) {
5590         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5591         gameInfo.boardWidth = appData.NrFiles;
5592     }
5593     if(appData.NrRanks >= 0) {
5594         gameInfo.boardHeight = appData.NrRanks;
5595     }
5596     if(appData.holdingsSize >= 0) {
5597         i = appData.holdingsSize;
5598         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5599         gameInfo.holdingsSize = i;
5600     }
5601     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5602     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5603         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5604
5605     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5606     if(pawnRow < 1) pawnRow = 1;
5607     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5608
5609     /* User pieceToChar list overrules defaults */
5610     if(appData.pieceToCharTable != NULL)
5611         SetCharTable(pieceToChar, appData.pieceToCharTable);
5612
5613     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5614
5615         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5616             s = (ChessSquare) 0; /* account holding counts in guard band */
5617         for( i=0; i<BOARD_HEIGHT; i++ )
5618             initialPosition[i][j] = s;
5619
5620         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5621         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5622         initialPosition[pawnRow][j] = WhitePawn;
5623         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5624         if(gameInfo.variant == VariantXiangqi) {
5625             if(j&1) {
5626                 initialPosition[pawnRow][j] =
5627                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5628                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5629                    initialPosition[2][j] = WhiteCannon;
5630                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5631                 }
5632             }
5633         }
5634         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5635     }
5636     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5637
5638             j=BOARD_LEFT+1;
5639             initialPosition[1][j] = WhiteBishop;
5640             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5641             j=BOARD_RGHT-2;
5642             initialPosition[1][j] = WhiteRook;
5643             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5644     }
5645
5646     if( nrCastlingRights == -1) {
5647         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5648         /*       This sets default castling rights from none to normal corners   */
5649         /* Variants with other castling rights must set them themselves above    */
5650         nrCastlingRights = 6;
5651
5652         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5653         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5654         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5655         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5656         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5657         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5658      }
5659
5660      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5661      if(gameInfo.variant == VariantGreat) { // promotion commoners
5662         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5663         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5664         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5665         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5666      }
5667      if( gameInfo.variant == VariantSChess ) {
5668       initialPosition[1][0] = BlackMarshall;
5669       initialPosition[2][0] = BlackAngel;
5670       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5671       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5672       initialPosition[1][1] = initialPosition[2][1] = 
5673       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5674      }
5675   if (appData.debugMode) {
5676     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5677   }
5678     if(shuffleOpenings) {
5679         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5680         startedFromSetupPosition = TRUE;
5681     }
5682     if(startedFromPositionFile) {
5683       /* [HGM] loadPos: use PositionFile for every new game */
5684       CopyBoard(initialPosition, filePosition);
5685       for(i=0; i<nrCastlingRights; i++)
5686           initialRights[i] = filePosition[CASTLING][i];
5687       startedFromSetupPosition = TRUE;
5688     }
5689
5690     CopyBoard(boards[0], initialPosition);
5691
5692     if(oldx != gameInfo.boardWidth ||
5693        oldy != gameInfo.boardHeight ||
5694        oldv != gameInfo.variant ||
5695        oldh != gameInfo.holdingsWidth
5696                                          )
5697             InitDrawingSizes(-2 ,0);
5698
5699     oldv = gameInfo.variant;
5700     if (redraw)
5701       DrawPosition(TRUE, boards[currentMove]);
5702 }
5703
5704 void
5705 SendBoard(cps, moveNum)
5706      ChessProgramState *cps;
5707      int moveNum;
5708 {
5709     char message[MSG_SIZ];
5710
5711     if (cps->useSetboard) {
5712       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5713       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5714       SendToProgram(message, cps);
5715       free(fen);
5716
5717     } else {
5718       ChessSquare *bp;
5719       int i, j;
5720       /* Kludge to set black to move, avoiding the troublesome and now
5721        * deprecated "black" command.
5722        */
5723       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5724         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5725
5726       SendToProgram("edit\n", cps);
5727       SendToProgram("#\n", cps);
5728       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5729         bp = &boards[moveNum][i][BOARD_LEFT];
5730         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5731           if ((int) *bp < (int) BlackPawn) {
5732             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5733                     AAA + j, ONE + i);
5734             if(message[0] == '+' || message[0] == '~') {
5735               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5736                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5737                         AAA + j, ONE + i);
5738             }
5739             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5740                 message[1] = BOARD_RGHT   - 1 - j + '1';
5741                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5742             }
5743             SendToProgram(message, cps);
5744           }
5745         }
5746       }
5747
5748       SendToProgram("c\n", cps);
5749       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5750         bp = &boards[moveNum][i][BOARD_LEFT];
5751         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5752           if (((int) *bp != (int) EmptySquare)
5753               && ((int) *bp >= (int) BlackPawn)) {
5754             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5755                     AAA + j, ONE + i);
5756             if(message[0] == '+' || message[0] == '~') {
5757               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5758                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5759                         AAA + j, ONE + i);
5760             }
5761             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5762                 message[1] = BOARD_RGHT   - 1 - j + '1';
5763                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5764             }
5765             SendToProgram(message, cps);
5766           }
5767         }
5768       }
5769
5770       SendToProgram(".\n", cps);
5771     }
5772     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5773 }
5774
5775 ChessSquare
5776 DefaultPromoChoice(int white)
5777 {
5778     ChessSquare result;
5779     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5780         result = WhiteFerz; // no choice
5781     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5782         result= WhiteKing; // in Suicide Q is the last thing we want
5783     else if(gameInfo.variant == VariantSpartan)
5784         result = white ? WhiteQueen : WhiteAngel;
5785     else result = WhiteQueen;
5786     if(!white) result = WHITE_TO_BLACK result;
5787     return result;
5788 }
5789
5790 static int autoQueen; // [HGM] oneclick
5791
5792 int
5793 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5794 {
5795     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5796     /* [HGM] add Shogi promotions */
5797     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5798     ChessSquare piece;
5799     ChessMove moveType;
5800     Boolean premove;
5801
5802     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5803     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5804
5805     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5806       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5807         return FALSE;
5808
5809     piece = boards[currentMove][fromY][fromX];
5810     if(gameInfo.variant == VariantShogi) {
5811         promotionZoneSize = BOARD_HEIGHT/3;
5812         highestPromotingPiece = (int)WhiteFerz;
5813     } else if(gameInfo.variant == VariantMakruk) {
5814         promotionZoneSize = 3;
5815     }
5816
5817     // Treat Lance as Pawn when it is not representing Amazon
5818     if(gameInfo.variant != VariantSuper) {
5819         if(piece == WhiteLance) piece = WhitePawn; else
5820         if(piece == BlackLance) piece = BlackPawn;
5821     }
5822
5823     // next weed out all moves that do not touch the promotion zone at all
5824     if((int)piece >= BlackPawn) {
5825         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5826              return FALSE;
5827         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5828     } else {
5829         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5830            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5831     }
5832
5833     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5834
5835     // weed out mandatory Shogi promotions
5836     if(gameInfo.variant == VariantShogi) {
5837         if(piece >= BlackPawn) {
5838             if(toY == 0 && piece == BlackPawn ||
5839                toY == 0 && piece == BlackQueen ||
5840                toY <= 1 && piece == BlackKnight) {
5841                 *promoChoice = '+';
5842                 return FALSE;
5843             }
5844         } else {
5845             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5846                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5847                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5848                 *promoChoice = '+';
5849                 return FALSE;
5850             }
5851         }
5852     }
5853
5854     // weed out obviously illegal Pawn moves
5855     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5856         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5857         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5858         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5859         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5860         // note we are not allowed to test for valid (non-)capture, due to premove
5861     }
5862
5863     // we either have a choice what to promote to, or (in Shogi) whether to promote
5864     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5865         *promoChoice = PieceToChar(BlackFerz);  // no choice
5866         return FALSE;
5867     }
5868     // no sense asking what we must promote to if it is going to explode...
5869     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5870         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5871         return FALSE;
5872     }
5873     // give caller the default choice even if we will not make it
5874     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5875     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5876     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5877                            && gameInfo.variant != VariantShogi
5878                            && gameInfo.variant != VariantSuper) return FALSE;
5879     if(autoQueen) return FALSE; // predetermined
5880
5881     // suppress promotion popup on illegal moves that are not premoves
5882     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5883               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5884     if(appData.testLegality && !premove) {
5885         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5886                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5887         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5888             return FALSE;
5889     }
5890
5891     return TRUE;
5892 }
5893
5894 int
5895 InPalace(row, column)
5896      int row, column;
5897 {   /* [HGM] for Xiangqi */
5898     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5899          column < (BOARD_WIDTH + 4)/2 &&
5900          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5901     return FALSE;
5902 }
5903
5904 int
5905 PieceForSquare (x, y)
5906      int x;
5907      int y;
5908 {
5909   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5910      return -1;
5911   else
5912      return boards[currentMove][y][x];
5913 }
5914
5915 int
5916 OKToStartUserMove(x, y)
5917      int x, y;
5918 {
5919     ChessSquare from_piece;
5920     int white_piece;
5921
5922     if (matchMode) return FALSE;
5923     if (gameMode == EditPosition) return TRUE;
5924
5925     if (x >= 0 && y >= 0)
5926       from_piece = boards[currentMove][y][x];
5927     else
5928       from_piece = EmptySquare;
5929
5930     if (from_piece == EmptySquare) return FALSE;
5931
5932     white_piece = (int)from_piece >= (int)WhitePawn &&
5933       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5934
5935     switch (gameMode) {
5936       case PlayFromGameFile:
5937       case AnalyzeFile:
5938       case TwoMachinesPlay:
5939       case EndOfGame:
5940         return FALSE;
5941
5942       case IcsObserving:
5943       case IcsIdle:
5944         return FALSE;
5945
5946       case MachinePlaysWhite:
5947       case IcsPlayingBlack:
5948         if (appData.zippyPlay) return FALSE;
5949         if (white_piece) {
5950             DisplayMoveError(_("You are playing Black"));
5951             return FALSE;
5952         }
5953         break;
5954
5955       case MachinePlaysBlack:
5956       case IcsPlayingWhite:
5957         if (appData.zippyPlay) return FALSE;
5958         if (!white_piece) {
5959             DisplayMoveError(_("You are playing White"));
5960             return FALSE;
5961         }
5962         break;
5963
5964       case EditGame:
5965         if (!white_piece && WhiteOnMove(currentMove)) {
5966             DisplayMoveError(_("It is White's turn"));
5967             return FALSE;
5968         }
5969         if (white_piece && !WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is Black's turn"));
5971             return FALSE;
5972         }
5973         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5974             /* Editing correspondence game history */
5975             /* Could disallow this or prompt for confirmation */
5976             cmailOldMove = -1;
5977         }
5978         break;
5979
5980       case BeginningOfGame:
5981         if (appData.icsActive) return FALSE;
5982         if (!appData.noChessProgram) {
5983             if (!white_piece) {
5984                 DisplayMoveError(_("You are playing White"));
5985                 return FALSE;
5986             }
5987         }
5988         break;
5989
5990       case Training:
5991         if (!white_piece && WhiteOnMove(currentMove)) {
5992             DisplayMoveError(_("It is White's turn"));
5993             return FALSE;
5994         }
5995         if (white_piece && !WhiteOnMove(currentMove)) {
5996             DisplayMoveError(_("It is Black's turn"));
5997             return FALSE;
5998         }
5999         break;
6000
6001       default:
6002       case IcsExamining:
6003         break;
6004     }
6005     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6006         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6007         && gameMode != AnalyzeFile && gameMode != Training) {
6008         DisplayMoveError(_("Displayed position is not current"));
6009         return FALSE;
6010     }
6011     return TRUE;
6012 }
6013
6014 Boolean
6015 OnlyMove(int *x, int *y, Boolean captures) {
6016     DisambiguateClosure cl;
6017     if (appData.zippyPlay) return FALSE;
6018     switch(gameMode) {
6019       case MachinePlaysBlack:
6020       case IcsPlayingWhite:
6021       case BeginningOfGame:
6022         if(!WhiteOnMove(currentMove)) return FALSE;
6023         break;
6024       case MachinePlaysWhite:
6025       case IcsPlayingBlack:
6026         if(WhiteOnMove(currentMove)) return FALSE;
6027         break;
6028       case EditGame:
6029         break;
6030       default:
6031         return FALSE;
6032     }
6033     cl.pieceIn = EmptySquare;
6034     cl.rfIn = *y;
6035     cl.ffIn = *x;
6036     cl.rtIn = -1;
6037     cl.ftIn = -1;
6038     cl.promoCharIn = NULLCHAR;
6039     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6040     if( cl.kind == NormalMove ||
6041         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6042         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6043         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6044       fromX = cl.ff;
6045       fromY = cl.rf;
6046       *x = cl.ft;
6047       *y = cl.rt;
6048       return TRUE;
6049     }
6050     if(cl.kind != ImpossibleMove) return FALSE;
6051     cl.pieceIn = EmptySquare;
6052     cl.rfIn = -1;
6053     cl.ffIn = -1;
6054     cl.rtIn = *y;
6055     cl.ftIn = *x;
6056     cl.promoCharIn = NULLCHAR;
6057     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6058     if( cl.kind == NormalMove ||
6059         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6060         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6061         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6062       fromX = cl.ff;
6063       fromY = cl.rf;
6064       *x = cl.ft;
6065       *y = cl.rt;
6066       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6067       return TRUE;
6068     }
6069     return FALSE;
6070 }
6071
6072 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6073 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6074 int lastLoadGameUseList = FALSE;
6075 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6076 ChessMove lastLoadGameStart = EndOfFile;
6077
6078 void
6079 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6080      int fromX, fromY, toX, toY;
6081      int promoChar;
6082 {
6083     ChessMove moveType;
6084     ChessSquare pdown, pup;
6085
6086     /* Check if the user is playing in turn.  This is complicated because we
6087        let the user "pick up" a piece before it is his turn.  So the piece he
6088        tried to pick up may have been captured by the time he puts it down!
6089        Therefore we use the color the user is supposed to be playing in this
6090        test, not the color of the piece that is currently on the starting
6091        square---except in EditGame mode, where the user is playing both
6092        sides; fortunately there the capture race can't happen.  (It can
6093        now happen in IcsExamining mode, but that's just too bad.  The user
6094        will get a somewhat confusing message in that case.)
6095        */
6096
6097     switch (gameMode) {
6098       case PlayFromGameFile:
6099       case AnalyzeFile:
6100       case TwoMachinesPlay:
6101       case EndOfGame:
6102       case IcsObserving:
6103       case IcsIdle:
6104         /* We switched into a game mode where moves are not accepted,
6105            perhaps while the mouse button was down. */
6106         return;
6107
6108       case MachinePlaysWhite:
6109         /* User is moving for Black */
6110         if (WhiteOnMove(currentMove)) {
6111             DisplayMoveError(_("It is White's turn"));
6112             return;
6113         }
6114         break;
6115
6116       case MachinePlaysBlack:
6117         /* User is moving for White */
6118         if (!WhiteOnMove(currentMove)) {
6119             DisplayMoveError(_("It is Black's turn"));
6120             return;
6121         }
6122         break;
6123
6124       case EditGame:
6125       case IcsExamining:
6126       case BeginningOfGame:
6127       case AnalyzeMode:
6128       case Training:
6129         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6130         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6131             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6132             /* User is moving for Black */
6133             if (WhiteOnMove(currentMove)) {
6134                 DisplayMoveError(_("It is White's turn"));
6135                 return;
6136             }
6137         } else {
6138             /* User is moving for White */
6139             if (!WhiteOnMove(currentMove)) {
6140                 DisplayMoveError(_("It is Black's turn"));
6141                 return;
6142             }
6143         }
6144         break;
6145
6146       case IcsPlayingBlack:
6147         /* User is moving for Black */
6148         if (WhiteOnMove(currentMove)) {
6149             if (!appData.premove) {
6150                 DisplayMoveError(_("It is White's turn"));
6151             } else if (toX >= 0 && toY >= 0) {
6152                 premoveToX = toX;
6153                 premoveToY = toY;
6154                 premoveFromX = fromX;
6155                 premoveFromY = fromY;
6156                 premovePromoChar = promoChar;
6157                 gotPremove = 1;
6158                 if (appData.debugMode)
6159                     fprintf(debugFP, "Got premove: fromX %d,"
6160                             "fromY %d, toX %d, toY %d\n",
6161                             fromX, fromY, toX, toY);
6162             }
6163             return;
6164         }
6165         break;
6166
6167       case IcsPlayingWhite:
6168         /* User is moving for White */
6169         if (!WhiteOnMove(currentMove)) {
6170             if (!appData.premove) {
6171                 DisplayMoveError(_("It is Black's turn"));
6172             } else if (toX >= 0 && toY >= 0) {
6173                 premoveToX = toX;
6174                 premoveToY = toY;
6175                 premoveFromX = fromX;
6176                 premoveFromY = fromY;
6177                 premovePromoChar = promoChar;
6178                 gotPremove = 1;
6179                 if (appData.debugMode)
6180                     fprintf(debugFP, "Got premove: fromX %d,"
6181                             "fromY %d, toX %d, toY %d\n",
6182                             fromX, fromY, toX, toY);
6183             }
6184             return;
6185         }
6186         break;
6187
6188       default:
6189         break;
6190
6191       case EditPosition:
6192         /* EditPosition, empty square, or different color piece;
6193            click-click move is possible */
6194         if (toX == -2 || toY == -2) {
6195             boards[0][fromY][fromX] = EmptySquare;
6196             DrawPosition(FALSE, boards[currentMove]);
6197             return;
6198         } else if (toX >= 0 && toY >= 0) {
6199             boards[0][toY][toX] = boards[0][fromY][fromX];
6200             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6201                 if(boards[0][fromY][0] != EmptySquare) {
6202                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6203                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6204                 }
6205             } else
6206             if(fromX == BOARD_RGHT+1) {
6207                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6208                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6209                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6210                 }
6211             } else
6212             boards[0][fromY][fromX] = EmptySquare;
6213             DrawPosition(FALSE, boards[currentMove]);
6214             return;
6215         }
6216         return;
6217     }
6218
6219     if(toX < 0 || toY < 0) return;
6220     pdown = boards[currentMove][fromY][fromX];
6221     pup = boards[currentMove][toY][toX];
6222
6223     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6224     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6225          if( pup != EmptySquare ) return;
6226          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6227            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6228                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6229            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6230            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6231            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6232            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6233          fromY = DROP_RANK;
6234     }
6235
6236     /* [HGM] always test for legality, to get promotion info */
6237     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6238                                          fromY, fromX, toY, toX, promoChar);
6239     /* [HGM] but possibly ignore an IllegalMove result */
6240     if (appData.testLegality) {
6241         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6242             DisplayMoveError(_("Illegal move"));
6243             return;
6244         }
6245     }
6246
6247     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6248 }
6249
6250 /* Common tail of UserMoveEvent and DropMenuEvent */
6251 int
6252 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6253      ChessMove moveType;
6254      int fromX, fromY, toX, toY;
6255      /*char*/int promoChar;
6256 {
6257     char *bookHit = 0;
6258
6259     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6260         // [HGM] superchess: suppress promotions to non-available piece
6261         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6262         if(WhiteOnMove(currentMove)) {
6263             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6264         } else {
6265             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6266         }
6267     }
6268
6269     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6270        move type in caller when we know the move is a legal promotion */
6271     if(moveType == NormalMove && promoChar)
6272         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6273
6274     /* [HGM] <popupFix> The following if has been moved here from
6275        UserMoveEvent(). Because it seemed to belong here (why not allow
6276        piece drops in training games?), and because it can only be
6277        performed after it is known to what we promote. */
6278     if (gameMode == Training) {
6279       /* compare the move played on the board to the next move in the
6280        * game. If they match, display the move and the opponent's response.
6281        * If they don't match, display an error message.
6282        */
6283       int saveAnimate;
6284       Board testBoard;
6285       CopyBoard(testBoard, boards[currentMove]);
6286       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6287
6288       if (CompareBoards(testBoard, boards[currentMove+1])) {
6289         ForwardInner(currentMove+1);
6290
6291         /* Autoplay the opponent's response.
6292          * if appData.animate was TRUE when Training mode was entered,
6293          * the response will be animated.
6294          */
6295         saveAnimate = appData.animate;
6296         appData.animate = animateTraining;
6297         ForwardInner(currentMove+1);
6298         appData.animate = saveAnimate;
6299
6300         /* check for the end of the game */
6301         if (currentMove >= forwardMostMove) {
6302           gameMode = PlayFromGameFile;
6303           ModeHighlight();
6304           SetTrainingModeOff();
6305           DisplayInformation(_("End of game"));
6306         }
6307       } else {
6308         DisplayError(_("Incorrect move"), 0);
6309       }
6310       return 1;
6311     }
6312
6313   /* Ok, now we know that the move is good, so we can kill
6314      the previous line in Analysis Mode */
6315   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6316                                 && currentMove < forwardMostMove) {
6317     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6318     else forwardMostMove = currentMove;
6319   }
6320
6321   /* If we need the chess program but it's dead, restart it */
6322   ResurrectChessProgram();
6323
6324   /* A user move restarts a paused game*/
6325   if (pausing)
6326     PauseEvent();
6327
6328   thinkOutput[0] = NULLCHAR;
6329
6330   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6331
6332   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6333     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6334     return 1;
6335   }
6336
6337   if (gameMode == BeginningOfGame) {
6338     if (appData.noChessProgram) {
6339       gameMode = EditGame;
6340       SetGameInfo();
6341     } else {
6342       char buf[MSG_SIZ];
6343       gameMode = MachinePlaysBlack;
6344       StartClocks();
6345       SetGameInfo();
6346       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6347       DisplayTitle(buf);
6348       if (first.sendName) {
6349         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6350         SendToProgram(buf, &first);
6351       }
6352       StartClocks();
6353     }
6354     ModeHighlight();
6355   }
6356
6357   /* Relay move to ICS or chess engine */
6358   if (appData.icsActive) {
6359     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6360         gameMode == IcsExamining) {
6361       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6362         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6363         SendToICS("draw ");
6364         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6365       }
6366       // also send plain move, in case ICS does not understand atomic claims
6367       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6368       ics_user_moved = 1;
6369     }
6370   } else {
6371     if (first.sendTime && (gameMode == BeginningOfGame ||
6372                            gameMode == MachinePlaysWhite ||
6373                            gameMode == MachinePlaysBlack)) {
6374       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6375     }
6376     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6377          // [HGM] book: if program might be playing, let it use book
6378         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6379         first.maybeThinking = TRUE;
6380     } else SendMoveToProgram(forwardMostMove-1, &first);
6381     if (currentMove == cmailOldMove + 1) {
6382       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6383     }
6384   }
6385
6386   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6387
6388   switch (gameMode) {
6389   case EditGame:
6390     if(appData.testLegality)
6391     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6392     case MT_NONE:
6393     case MT_CHECK:
6394       break;
6395     case MT_CHECKMATE:
6396     case MT_STAINMATE:
6397       if (WhiteOnMove(currentMove)) {
6398         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6399       } else {
6400         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6401       }
6402       break;
6403     case MT_STALEMATE:
6404       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6405       break;
6406     }
6407     break;
6408
6409   case MachinePlaysBlack:
6410   case MachinePlaysWhite:
6411     /* disable certain menu options while machine is thinking */
6412     SetMachineThinkingEnables();
6413     break;
6414
6415   default:
6416     break;
6417   }
6418
6419   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6420   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6421
6422   if(bookHit) { // [HGM] book: simulate book reply
6423         static char bookMove[MSG_SIZ]; // a bit generous?
6424
6425         programStats.nodes = programStats.depth = programStats.time =
6426         programStats.score = programStats.got_only_move = 0;
6427         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6428
6429         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6430         strcat(bookMove, bookHit);
6431         HandleMachineMove(bookMove, &first);
6432   }
6433   return 1;
6434 }
6435
6436 void
6437 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6438      Board board;
6439      int flags;
6440      ChessMove kind;
6441      int rf, ff, rt, ft;
6442      VOIDSTAR closure;
6443 {
6444     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6445     Markers *m = (Markers *) closure;
6446     if(rf == fromY && ff == fromX)
6447         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6448                          || kind == WhiteCapturesEnPassant
6449                          || kind == BlackCapturesEnPassant);
6450     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6451 }
6452
6453 void
6454 MarkTargetSquares(int clear)
6455 {
6456   int x, y;
6457   if(!appData.markers || !appData.highlightDragging ||
6458      !appData.testLegality || gameMode == EditPosition) return;
6459   if(clear) {
6460     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6461   } else {
6462     int capt = 0;
6463     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6464     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6465       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6466       if(capt)
6467       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6468     }
6469   }
6470   DrawPosition(TRUE, NULL);
6471 }
6472
6473 int
6474 Explode(Board board, int fromX, int fromY, int toX, int toY)
6475 {
6476     if(gameInfo.variant == VariantAtomic &&
6477        (board[toY][toX] != EmptySquare ||                     // capture?
6478         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6479                          board[fromY][fromX] == BlackPawn   )
6480       )) {
6481         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6482         return TRUE;
6483     }
6484     return FALSE;
6485 }
6486
6487 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6488
6489 int CanPromote(ChessSquare piece, int y)
6490 {
6491         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6492         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6493         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6494            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6495            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6496                                                   gameInfo.variant == VariantMakruk) return FALSE;
6497         return (piece == BlackPawn && y == 1 ||
6498                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6499                 piece == BlackLance && y == 1 ||
6500                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6501 }
6502
6503 void LeftClick(ClickType clickType, int xPix, int yPix)
6504 {
6505     int x, y;
6506     Boolean saveAnimate;
6507     static int second = 0, promotionChoice = 0, clearFlag = 0;
6508     char promoChoice = NULLCHAR;
6509     ChessSquare piece;
6510
6511     if(appData.seekGraph && appData.icsActive && loggedOn &&
6512         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6513         SeekGraphClick(clickType, xPix, yPix, 0);
6514         return;
6515     }
6516
6517     if (clickType == Press) ErrorPopDown();
6518     MarkTargetSquares(1);
6519
6520     x = EventToSquare(xPix, BOARD_WIDTH);
6521     y = EventToSquare(yPix, BOARD_HEIGHT);
6522     if (!flipView && y >= 0) {
6523         y = BOARD_HEIGHT - 1 - y;
6524     }
6525     if (flipView && x >= 0) {
6526         x = BOARD_WIDTH - 1 - x;
6527     }
6528
6529     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6530         defaultPromoChoice = promoSweep;
6531         promoSweep = EmptySquare;   // terminate sweep
6532         promoDefaultAltered = TRUE;
6533         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6534     }
6535
6536     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6537         if(clickType == Release) return; // ignore upclick of click-click destination
6538         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6539         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6540         if(gameInfo.holdingsWidth &&
6541                 (WhiteOnMove(currentMove)
6542                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6543                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6544             // click in right holdings, for determining promotion piece
6545             ChessSquare p = boards[currentMove][y][x];
6546             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6547             if(p != EmptySquare) {
6548                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6549                 fromX = fromY = -1;
6550                 return;
6551             }
6552         }
6553         DrawPosition(FALSE, boards[currentMove]);
6554         return;
6555     }
6556
6557     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6558     if(clickType == Press
6559             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6560               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6561               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6562         return;
6563
6564     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6565         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6566
6567     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6568         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6569                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6570         defaultPromoChoice = DefaultPromoChoice(side);
6571     }
6572
6573     autoQueen = appData.alwaysPromoteToQueen;
6574
6575     if (fromX == -1) {
6576       int originalY = y;
6577       gatingPiece = EmptySquare;
6578       if (clickType != Press) {
6579         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6580             DragPieceEnd(xPix, yPix); dragging = 0;
6581             DrawPosition(FALSE, NULL);
6582         }
6583         return;
6584       }
6585       fromX = x; fromY = y;
6586       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6587          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6588          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6589             /* First square */
6590             if (OKToStartUserMove(fromX, fromY)) {
6591                 second = 0;
6592                 MarkTargetSquares(0);
6593                 DragPieceBegin(xPix, yPix); dragging = 1;
6594                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6595                     promoSweep = defaultPromoChoice;
6596                     selectFlag = 0; lastX = xPix; lastY = yPix;
6597                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6598                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6599                 }
6600                 if (appData.highlightDragging) {
6601                     SetHighlights(fromX, fromY, -1, -1);
6602                 }
6603             } else fromX = fromY = -1;
6604             return;
6605         }
6606     }
6607
6608     /* fromX != -1 */
6609     if (clickType == Press && gameMode != EditPosition) {
6610         ChessSquare fromP;
6611         ChessSquare toP;
6612         int frc;
6613
6614         // ignore off-board to clicks
6615         if(y < 0 || x < 0) return;
6616
6617         /* Check if clicking again on the same color piece */
6618         fromP = boards[currentMove][fromY][fromX];
6619         toP = boards[currentMove][y][x];
6620         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6621         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6622              WhitePawn <= toP && toP <= WhiteKing &&
6623              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6624              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6625             (BlackPawn <= fromP && fromP <= BlackKing &&
6626              BlackPawn <= toP && toP <= BlackKing &&
6627              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6628              !(fromP == BlackKing && toP == BlackRook && frc))) {
6629             /* Clicked again on same color piece -- changed his mind */
6630             second = (x == fromX && y == fromY);
6631             promoDefaultAltered = FALSE;
6632            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6633             if (appData.highlightDragging) {
6634                 SetHighlights(x, y, -1, -1);
6635             } else {
6636                 ClearHighlights();
6637             }
6638             if (OKToStartUserMove(x, y)) {
6639                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6640                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6641                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6642                  gatingPiece = boards[currentMove][fromY][fromX];
6643                 else gatingPiece = EmptySquare;
6644                 fromX = x;
6645                 fromY = y; dragging = 1;
6646                 MarkTargetSquares(0);
6647                 DragPieceBegin(xPix, yPix);
6648                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6649                     promoSweep = defaultPromoChoice;
6650                     selectFlag = 0; lastX = xPix; lastY = yPix;
6651                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6652                 }
6653             }
6654            }
6655            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6656            second = FALSE; 
6657         }
6658         // ignore clicks on holdings
6659         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6660     }
6661
6662     if (clickType == Release && x == fromX && y == fromY) {
6663         DragPieceEnd(xPix, yPix); dragging = 0;
6664         if(clearFlag) {
6665             // a deferred attempt to click-click move an empty square on top of a piece
6666             boards[currentMove][y][x] = EmptySquare;
6667             ClearHighlights();
6668             DrawPosition(FALSE, boards[currentMove]);
6669             fromX = fromY = -1; clearFlag = 0;
6670             return;
6671         }
6672         if (appData.animateDragging) {
6673             /* Undo animation damage if any */
6674             DrawPosition(FALSE, NULL);
6675         }
6676         if (second) {
6677             /* Second up/down in same square; just abort move */
6678             second = 0;
6679             fromX = fromY = -1;
6680             gatingPiece = EmptySquare;
6681             ClearHighlights();
6682             gotPremove = 0;
6683             ClearPremoveHighlights();
6684         } else {
6685             /* First upclick in same square; start click-click mode */
6686             SetHighlights(x, y, -1, -1);
6687         }
6688         return;
6689     }
6690
6691     clearFlag = 0;
6692
6693     /* we now have a different from- and (possibly off-board) to-square */
6694     /* Completed move */
6695     toX = x;
6696     toY = y;
6697     saveAnimate = appData.animate;
6698     if (clickType == Press) {
6699         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6700             // must be Edit Position mode with empty-square selected
6701             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6702             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6703             return;
6704         }
6705         /* Finish clickclick move */
6706         if (appData.animate || appData.highlightLastMove) {
6707             SetHighlights(fromX, fromY, toX, toY);
6708         } else {
6709             ClearHighlights();
6710         }
6711     } else {
6712         /* Finish drag move */
6713         if (appData.highlightLastMove) {
6714             SetHighlights(fromX, fromY, toX, toY);
6715         } else {
6716             ClearHighlights();
6717         }
6718         DragPieceEnd(xPix, yPix); dragging = 0;
6719         /* Don't animate move and drag both */
6720         appData.animate = FALSE;
6721     }
6722
6723     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6724     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6725         ChessSquare piece = boards[currentMove][fromY][fromX];
6726         if(gameMode == EditPosition && piece != EmptySquare &&
6727            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6728             int n;
6729
6730             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6731                 n = PieceToNumber(piece - (int)BlackPawn);
6732                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6733                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6734                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6735             } else
6736             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6737                 n = PieceToNumber(piece);
6738                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6739                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6740                 boards[currentMove][n][BOARD_WIDTH-2]++;
6741             }
6742             boards[currentMove][fromY][fromX] = EmptySquare;
6743         }
6744         ClearHighlights();
6745         fromX = fromY = -1;
6746         DrawPosition(TRUE, boards[currentMove]);
6747         return;
6748     }
6749
6750     // off-board moves should not be highlighted
6751     if(x < 0 || y < 0) ClearHighlights();
6752
6753     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6754
6755     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6756         SetHighlights(fromX, fromY, toX, toY);
6757         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6758             // [HGM] super: promotion to captured piece selected from holdings
6759             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6760             promotionChoice = TRUE;
6761             // kludge follows to temporarily execute move on display, without promoting yet
6762             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6763             boards[currentMove][toY][toX] = p;
6764             DrawPosition(FALSE, boards[currentMove]);
6765             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6766             boards[currentMove][toY][toX] = q;
6767             DisplayMessage("Click in holdings to choose piece", "");
6768             return;
6769         }
6770         PromotionPopUp();
6771     } else {
6772         int oldMove = currentMove;
6773         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6774         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6775         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6776         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6777            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6778             DrawPosition(TRUE, boards[currentMove]);
6779         fromX = fromY = -1;
6780     }
6781     appData.animate = saveAnimate;
6782     if (appData.animate || appData.animateDragging) {
6783         /* Undo animation damage if needed */
6784         DrawPosition(FALSE, NULL);
6785     }
6786 }
6787
6788 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6789 {   // front-end-free part taken out of PieceMenuPopup
6790     int whichMenu; int xSqr, ySqr;
6791
6792     if(seekGraphUp) { // [HGM] seekgraph
6793         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6794         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6795         return -2;
6796     }
6797
6798     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6799          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6800         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6801         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6802         if(action == Press)   {
6803             originalFlip = flipView;
6804             flipView = !flipView; // temporarily flip board to see game from partners perspective
6805             DrawPosition(TRUE, partnerBoard);
6806             DisplayMessage(partnerStatus, "");
6807             partnerUp = TRUE;
6808         } else if(action == Release) {
6809             flipView = originalFlip;
6810             DrawPosition(TRUE, boards[currentMove]);
6811             partnerUp = FALSE;
6812         }
6813         return -2;
6814     }
6815
6816     xSqr = EventToSquare(x, BOARD_WIDTH);
6817     ySqr = EventToSquare(y, BOARD_HEIGHT);
6818     if (action == Release) {
6819         if(pieceSweep != EmptySquare) {
6820             EditPositionMenuEvent(pieceSweep, toX, toY);
6821             pieceSweep = EmptySquare;
6822         } else UnLoadPV(); // [HGM] pv
6823     }
6824     if (action != Press) return -2; // return code to be ignored
6825     switch (gameMode) {
6826       case IcsExamining:
6827         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6828       case EditPosition:
6829         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6830         if (xSqr < 0 || ySqr < 0) return -1;
6831         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6832         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6833         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6834         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6835         NextPiece(0);
6836         return -2;\r
6837       case IcsObserving:
6838         if(!appData.icsEngineAnalyze) return -1;
6839       case IcsPlayingWhite:
6840       case IcsPlayingBlack:
6841         if(!appData.zippyPlay) goto noZip;
6842       case AnalyzeMode:
6843       case AnalyzeFile:
6844       case MachinePlaysWhite:
6845       case MachinePlaysBlack:
6846       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6847         if (!appData.dropMenu) {
6848           LoadPV(x, y);
6849           return 2; // flag front-end to grab mouse events
6850         }
6851         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6852            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6853       case EditGame:
6854       noZip:
6855         if (xSqr < 0 || ySqr < 0) return -1;
6856         if (!appData.dropMenu || appData.testLegality &&
6857             gameInfo.variant != VariantBughouse &&
6858             gameInfo.variant != VariantCrazyhouse) return -1;
6859         whichMenu = 1; // drop menu
6860         break;
6861       default:
6862         return -1;
6863     }
6864
6865     if (((*fromX = xSqr) < 0) ||
6866         ((*fromY = ySqr) < 0)) {
6867         *fromX = *fromY = -1;
6868         return -1;
6869     }
6870     if (flipView)
6871       *fromX = BOARD_WIDTH - 1 - *fromX;
6872     else
6873       *fromY = BOARD_HEIGHT - 1 - *fromY;
6874
6875     return whichMenu;
6876 }
6877
6878 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6879 {
6880 //    char * hint = lastHint;
6881     FrontEndProgramStats stats;
6882
6883     stats.which = cps == &first ? 0 : 1;
6884     stats.depth = cpstats->depth;
6885     stats.nodes = cpstats->nodes;
6886     stats.score = cpstats->score;
6887     stats.time = cpstats->time;
6888     stats.pv = cpstats->movelist;
6889     stats.hint = lastHint;
6890     stats.an_move_index = 0;
6891     stats.an_move_count = 0;
6892
6893     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6894         stats.hint = cpstats->move_name;
6895         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6896         stats.an_move_count = cpstats->nr_moves;
6897     }
6898
6899     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
6900
6901     SetProgramStats( &stats );
6902 }
6903
6904 void
6905 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6906 {       // count all piece types
6907         int p, f, r;
6908         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6909         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6910         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6911                 p = board[r][f];
6912                 pCnt[p]++;
6913                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6914                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6915                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6916                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6917                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6918                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6919         }
6920 }
6921
6922 int
6923 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6924 {
6925         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6926         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6927
6928         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6929         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6930         if(myPawns == 2 && nMine == 3) // KPP
6931             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6932         if(myPawns == 1 && nMine == 2) // KP
6933             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6934         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6935             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6936         if(myPawns) return FALSE;
6937         if(pCnt[WhiteRook+side])
6938             return pCnt[BlackRook-side] ||
6939                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6940                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6941                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6942         if(pCnt[WhiteCannon+side]) {
6943             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6944             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6945         }
6946         if(pCnt[WhiteKnight+side])
6947             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6948         return FALSE;
6949 }
6950
6951 int
6952 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6953 {
6954         VariantClass v = gameInfo.variant;
6955
6956         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6957         if(v == VariantShatranj) return TRUE; // always winnable through baring
6958         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6959         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6960
6961         if(v == VariantXiangqi) {
6962                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6963
6964                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6965                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6966                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6967                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6968                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6969                 if(stale) // we have at least one last-rank P plus perhaps C
6970                     return majors // KPKX
6971                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6972                 else // KCA*E*
6973                     return pCnt[WhiteFerz+side] // KCAK
6974                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6975                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6976                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6977
6978         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6979                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6980
6981                 if(nMine == 1) return FALSE; // bare King
6982                 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
6983                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6984                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6985                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6986                 if(pCnt[WhiteKnight+side])
6987                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6988                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6989                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6990                 if(nBishops)
6991                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6992                 if(pCnt[WhiteAlfil+side])
6993                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6994                 if(pCnt[WhiteWazir+side])
6995                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6996         }
6997
6998         return TRUE;
6999 }
7000
7001 int
7002 Adjudicate(ChessProgramState *cps)
7003 {       // [HGM] some adjudications useful with buggy engines
7004         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7005         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7006         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7007         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7008         int k, count = 0; static int bare = 1;
7009         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7010         Boolean canAdjudicate = !appData.icsActive;
7011
7012         // most tests only when we understand the game, i.e. legality-checking on
7013             if( appData.testLegality )
7014             {   /* [HGM] Some more adjudications for obstinate engines */
7015                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7016                 static int moveCount = 6;
7017                 ChessMove result;
7018                 char *reason = NULL;
7019
7020                 /* Count what is on board. */
7021                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7022
7023                 /* Some material-based adjudications that have to be made before stalemate test */
7024                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7025                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7026                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7027                      if(canAdjudicate && appData.checkMates) {
7028                          if(engineOpponent)
7029                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7030                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7031                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7032                          return 1;
7033                      }
7034                 }
7035
7036                 /* Bare King in Shatranj (loses) or Losers (wins) */
7037                 if( nrW == 1 || nrB == 1) {
7038                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7039                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7040                      if(canAdjudicate && appData.checkMates) {
7041                          if(engineOpponent)
7042                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7043                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7044                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7045                          return 1;
7046                      }
7047                   } else
7048                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7049                   {    /* bare King */
7050                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7051                         if(canAdjudicate && appData.checkMates) {
7052                             /* but only adjudicate if adjudication enabled */
7053                             if(engineOpponent)
7054                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7055                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7056                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7057                             return 1;
7058                         }
7059                   }
7060                 } else bare = 1;
7061
7062
7063             // don't wait for engine to announce game end if we can judge ourselves
7064             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7065               case MT_CHECK:
7066                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7067                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7068                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7069                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7070                             checkCnt++;
7071                         if(checkCnt >= 2) {
7072                             reason = "Xboard adjudication: 3rd check";
7073                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7074                             break;
7075                         }
7076                     }
7077                 }
7078               case MT_NONE:
7079               default:
7080                 break;
7081               case MT_STALEMATE:
7082               case MT_STAINMATE:
7083                 reason = "Xboard adjudication: Stalemate";
7084                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7085                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7086                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7087                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7088                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7089                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7090                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7091                                                                         EP_CHECKMATE : EP_WINS);
7092                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7093                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7094                 }
7095                 break;
7096               case MT_CHECKMATE:
7097                 reason = "Xboard adjudication: Checkmate";
7098                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7099                 break;
7100             }
7101
7102                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7103                     case EP_STALEMATE:
7104                         result = GameIsDrawn; break;
7105                     case EP_CHECKMATE:
7106                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7107                     case EP_WINS:
7108                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7109                     default:
7110                         result = EndOfFile;
7111                 }
7112                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7113                     if(engineOpponent)
7114                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7115                     GameEnds( result, reason, GE_XBOARD );
7116                     return 1;
7117                 }
7118
7119                 /* Next absolutely insufficient mating material. */
7120                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7121                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7122                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7123
7124                      /* always flag draws, for judging claims */
7125                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7126
7127                      if(canAdjudicate && appData.materialDraws) {
7128                          /* but only adjudicate them if adjudication enabled */
7129                          if(engineOpponent) {
7130                            SendToProgram("force\n", engineOpponent); // suppress reply
7131                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7132                          }
7133                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7134                          return 1;
7135                      }
7136                 }
7137
7138                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7139                 if(gameInfo.variant == VariantXiangqi ?
7140                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7141                  : nrW + nrB == 4 &&
7142                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7143                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7144                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7145                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7146                    ) ) {
7147                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7148                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7149                           if(engineOpponent) {
7150                             SendToProgram("force\n", engineOpponent); // suppress reply
7151                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7152                           }
7153                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7154                           return 1;
7155                      }
7156                 } else moveCount = 6;
7157             }
7158         if (appData.debugMode) { int i;
7159             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7160                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7161                     appData.drawRepeats);
7162             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7163               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7164
7165         }
7166
7167         // Repetition draws and 50-move rule can be applied independently of legality testing
7168
7169                 /* Check for rep-draws */
7170                 count = 0;
7171                 for(k = forwardMostMove-2;
7172                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7173                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7174                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7175                     k-=2)
7176                 {   int rights=0;
7177                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7178                         /* compare castling rights */
7179                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7180                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7181                                 rights++; /* King lost rights, while rook still had them */
7182                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7183                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7184                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7185                                    rights++; /* but at least one rook lost them */
7186                         }
7187                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7188                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7189                                 rights++;
7190                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7191                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7192                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7193                                    rights++;
7194                         }
7195                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7196                             && appData.drawRepeats > 1) {
7197                              /* adjudicate after user-specified nr of repeats */
7198                              int result = GameIsDrawn;
7199                              char *details = "XBoard adjudication: repetition draw";
7200                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7201                                 // [HGM] xiangqi: check for forbidden perpetuals
7202                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7203                                 for(m=forwardMostMove; m>k; m-=2) {
7204                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7205                                         ourPerpetual = 0; // the current mover did not always check
7206                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7207                                         hisPerpetual = 0; // the opponent did not always check
7208                                 }
7209                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7210                                                                         ourPerpetual, hisPerpetual);
7211                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7212                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7213                                     details = "Xboard adjudication: perpetual checking";
7214                                 } else
7215                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7216                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7217                                 } else
7218                                 // Now check for perpetual chases
7219                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7220                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7221                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7222                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7223                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7224                                         details = "Xboard adjudication: perpetual chasing";
7225                                     } else
7226                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7227                                         break; // Abort repetition-checking loop.
7228                                 }
7229                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7230                              }
7231                              if(engineOpponent) {
7232                                SendToProgram("force\n", engineOpponent); // suppress reply
7233                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7234                              }
7235                              GameEnds( result, details, GE_XBOARD );
7236                              return 1;
7237                         }
7238                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7239                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7240                     }
7241                 }
7242
7243                 /* Now we test for 50-move draws. Determine ply count */
7244                 count = forwardMostMove;
7245                 /* look for last irreversble move */
7246                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7247                     count--;
7248                 /* if we hit starting position, add initial plies */
7249                 if( count == backwardMostMove )
7250                     count -= initialRulePlies;
7251                 count = forwardMostMove - count;
7252                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7253                         // adjust reversible move counter for checks in Xiangqi
7254                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7255                         if(i < backwardMostMove) i = backwardMostMove;
7256                         while(i <= forwardMostMove) {
7257                                 lastCheck = inCheck; // check evasion does not count
7258                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7259                                 if(inCheck || lastCheck) count--; // check does not count
7260                                 i++;
7261                         }
7262                 }
7263                 if( count >= 100)
7264                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7265                          /* this is used to judge if draw claims are legal */
7266                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7267                          if(engineOpponent) {
7268                            SendToProgram("force\n", engineOpponent); // suppress reply
7269                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7270                          }
7271                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7272                          return 1;
7273                 }
7274
7275                 /* if draw offer is pending, treat it as a draw claim
7276                  * when draw condition present, to allow engines a way to
7277                  * claim draws before making their move to avoid a race
7278                  * condition occurring after their move
7279                  */
7280                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7281                          char *p = NULL;
7282                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7283                              p = "Draw claim: 50-move rule";
7284                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7285                              p = "Draw claim: 3-fold repetition";
7286                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7287                              p = "Draw claim: insufficient mating material";
7288                          if( p != NULL && canAdjudicate) {
7289                              if(engineOpponent) {
7290                                SendToProgram("force\n", engineOpponent); // suppress reply
7291                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7292                              }
7293                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7294                              return 1;
7295                          }
7296                 }
7297
7298                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7299                     if(engineOpponent) {
7300                       SendToProgram("force\n", engineOpponent); // suppress reply
7301                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7302                     }
7303                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7304                     return 1;
7305                 }
7306         return 0;
7307 }
7308
7309 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7310 {   // [HGM] book: this routine intercepts moves to simulate book replies
7311     char *bookHit = NULL;
7312
7313     //first determine if the incoming move brings opponent into his book
7314     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7315         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7316     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7317     if(bookHit != NULL && !cps->bookSuspend) {
7318         // make sure opponent is not going to reply after receiving move to book position
7319         SendToProgram("force\n", cps);
7320         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7321     }
7322     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7323     // now arrange restart after book miss
7324     if(bookHit) {
7325         // after a book hit we never send 'go', and the code after the call to this routine
7326         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7327         char buf[MSG_SIZ];
7328         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7329         SendToProgram(buf, cps);
7330         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7331     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7332         SendToProgram("go\n", cps);
7333         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7334     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7335         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7336             SendToProgram("go\n", cps);
7337         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7338     }
7339     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7340 }
7341
7342 char *savedMessage;
7343 ChessProgramState *savedState;
7344 void DeferredBookMove(void)
7345 {
7346         if(savedState->lastPing != savedState->lastPong)
7347                     ScheduleDelayedEvent(DeferredBookMove, 10);
7348         else
7349         HandleMachineMove(savedMessage, savedState);
7350 }
7351
7352 void
7353 HandleMachineMove(message, cps)
7354      char *message;
7355      ChessProgramState *cps;
7356 {
7357     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7358     char realname[MSG_SIZ];
7359     int fromX, fromY, toX, toY;
7360     ChessMove moveType;
7361     char promoChar;
7362     char *p;
7363     int machineWhite;
7364     char *bookHit;
7365
7366     cps->userError = 0;
7367
7368 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7369     /*
7370      * Kludge to ignore BEL characters
7371      */
7372     while (*message == '\007') message++;
7373
7374     /*
7375      * [HGM] engine debug message: ignore lines starting with '#' character
7376      */
7377     if(cps->debug && *message == '#') return;
7378
7379     /*
7380      * Look for book output
7381      */
7382     if (cps == &first && bookRequested) {
7383         if (message[0] == '\t' || message[0] == ' ') {
7384             /* Part of the book output is here; append it */
7385             strcat(bookOutput, message);
7386             strcat(bookOutput, "  \n");
7387             return;
7388         } else if (bookOutput[0] != NULLCHAR) {
7389             /* All of book output has arrived; display it */
7390             char *p = bookOutput;
7391             while (*p != NULLCHAR) {
7392                 if (*p == '\t') *p = ' ';
7393                 p++;
7394             }
7395             DisplayInformation(bookOutput);
7396             bookRequested = FALSE;
7397             /* Fall through to parse the current output */
7398         }
7399     }
7400
7401     /*
7402      * Look for machine move.
7403      */
7404     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7405         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7406     {
7407         /* This method is only useful on engines that support ping */
7408         if (cps->lastPing != cps->lastPong) {
7409           if (gameMode == BeginningOfGame) {
7410             /* Extra move from before last new; ignore */
7411             if (appData.debugMode) {
7412                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7413             }
7414           } else {
7415             if (appData.debugMode) {
7416                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7417                         cps->which, gameMode);
7418             }
7419
7420             SendToProgram("undo\n", cps);
7421           }
7422           return;
7423         }
7424
7425         switch (gameMode) {
7426           case BeginningOfGame:
7427             /* Extra move from before last reset; ignore */
7428             if (appData.debugMode) {
7429                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7430             }
7431             return;
7432
7433           case EndOfGame:
7434           case IcsIdle:
7435           default:
7436             /* Extra move after we tried to stop.  The mode test is
7437                not a reliable way of detecting this problem, but it's
7438                the best we can do on engines that don't support ping.
7439             */
7440             if (appData.debugMode) {
7441                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7442                         cps->which, gameMode);
7443             }
7444             SendToProgram("undo\n", cps);
7445             return;
7446
7447           case MachinePlaysWhite:
7448           case IcsPlayingWhite:
7449             machineWhite = TRUE;
7450             break;
7451
7452           case MachinePlaysBlack:
7453           case IcsPlayingBlack:
7454             machineWhite = FALSE;
7455             break;
7456
7457           case TwoMachinesPlay:
7458             machineWhite = (cps->twoMachinesColor[0] == 'w');
7459             break;
7460         }
7461         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7462             if (appData.debugMode) {
7463                 fprintf(debugFP,
7464                         "Ignoring move out of turn by %s, gameMode %d"
7465                         ", forwardMost %d\n",
7466                         cps->which, gameMode, forwardMostMove);
7467             }
7468             return;
7469         }
7470
7471     if (appData.debugMode) { int f = forwardMostMove;
7472         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7473                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7474                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7475     }
7476         if(cps->alphaRank) AlphaRank(machineMove, 4);
7477         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7478                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7479             /* Machine move could not be parsed; ignore it. */
7480           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7481                     machineMove, _(cps->which));
7482             DisplayError(buf1, 0);
7483             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7484                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7485             if (gameMode == TwoMachinesPlay) {
7486               GameEnds(machineWhite ? BlackWins : WhiteWins,
7487                        buf1, GE_XBOARD);
7488             }
7489             return;
7490         }
7491
7492         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7493         /* So we have to redo legality test with true e.p. status here,  */
7494         /* to make sure an illegal e.p. capture does not slip through,   */
7495         /* to cause a forfeit on a justified illegal-move complaint      */
7496         /* of the opponent.                                              */
7497         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7498            ChessMove moveType;
7499            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7500                              fromY, fromX, toY, toX, promoChar);
7501             if (appData.debugMode) {
7502                 int i;
7503                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7504                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7505                 fprintf(debugFP, "castling rights\n");
7506             }
7507             if(moveType == IllegalMove) {
7508               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7509                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7510                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7511                            buf1, GE_XBOARD);
7512                 return;
7513            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7514            /* [HGM] Kludge to handle engines that send FRC-style castling
7515               when they shouldn't (like TSCP-Gothic) */
7516            switch(moveType) {
7517              case WhiteASideCastleFR:
7518              case BlackASideCastleFR:
7519                toX+=2;
7520                currentMoveString[2]++;
7521                break;
7522              case WhiteHSideCastleFR:
7523              case BlackHSideCastleFR:
7524                toX--;
7525                currentMoveString[2]--;
7526                break;
7527              default: ; // nothing to do, but suppresses warning of pedantic compilers
7528            }
7529         }
7530         hintRequested = FALSE;
7531         lastHint[0] = NULLCHAR;
7532         bookRequested = FALSE;
7533         /* Program may be pondering now */
7534         cps->maybeThinking = TRUE;
7535         if (cps->sendTime == 2) cps->sendTime = 1;
7536         if (cps->offeredDraw) cps->offeredDraw--;
7537
7538         /* [AS] Save move info*/
7539         pvInfoList[ forwardMostMove ].score = programStats.score;
7540         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7541         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7542
7543         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7544
7545         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7546         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7547             int count = 0;
7548
7549             while( count < adjudicateLossPlies ) {
7550                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7551
7552                 if( count & 1 ) {
7553                     score = -score; /* Flip score for winning side */
7554                 }
7555
7556                 if( score > adjudicateLossThreshold ) {
7557                     break;
7558                 }
7559
7560                 count++;
7561             }
7562
7563             if( count >= adjudicateLossPlies ) {
7564                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7565
7566                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7567                     "Xboard adjudication",
7568                     GE_XBOARD );
7569
7570                 return;
7571             }
7572         }
7573
7574         if(Adjudicate(cps)) {
7575             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7576             return; // [HGM] adjudicate: for all automatic game ends
7577         }
7578
7579 #if ZIPPY
7580         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7581             first.initDone) {
7582           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7583                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7584                 SendToICS("draw ");
7585                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7586           }
7587           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7588           ics_user_moved = 1;
7589           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7590                 char buf[3*MSG_SIZ];
7591
7592                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7593                         programStats.score / 100.,
7594                         programStats.depth,
7595                         programStats.time / 100.,
7596                         (unsigned int)programStats.nodes,
7597                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7598                         programStats.movelist);
7599                 SendToICS(buf);
7600 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7601           }
7602         }
7603 #endif
7604
7605         /* [AS] Clear stats for next move */
7606         ClearProgramStats();
7607         thinkOutput[0] = NULLCHAR;
7608         hiddenThinkOutputState = 0;
7609
7610         bookHit = NULL;
7611         if (gameMode == TwoMachinesPlay) {
7612             /* [HGM] relaying draw offers moved to after reception of move */
7613             /* and interpreting offer as claim if it brings draw condition */
7614             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7615                 SendToProgram("draw\n", cps->other);
7616             }
7617             if (cps->other->sendTime) {
7618                 SendTimeRemaining(cps->other,
7619                                   cps->other->twoMachinesColor[0] == 'w');
7620             }
7621             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7622             if (firstMove && !bookHit) {
7623                 firstMove = FALSE;
7624                 if (cps->other->useColors) {
7625                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7626                 }
7627                 SendToProgram("go\n", cps->other);
7628             }
7629             cps->other->maybeThinking = TRUE;
7630         }
7631
7632         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7633
7634         if (!pausing && appData.ringBellAfterMoves) {
7635             RingBell();
7636         }
7637
7638         /*
7639          * Reenable menu items that were disabled while
7640          * machine was thinking
7641          */
7642         if (gameMode != TwoMachinesPlay)
7643             SetUserThinkingEnables();
7644
7645         // [HGM] book: after book hit opponent has received move and is now in force mode
7646         // force the book reply into it, and then fake that it outputted this move by jumping
7647         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7648         if(bookHit) {
7649                 static char bookMove[MSG_SIZ]; // a bit generous?
7650
7651                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7652                 strcat(bookMove, bookHit);
7653                 message = bookMove;
7654                 cps = cps->other;
7655                 programStats.nodes = programStats.depth = programStats.time =
7656                 programStats.score = programStats.got_only_move = 0;
7657                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7658
7659                 if(cps->lastPing != cps->lastPong) {
7660                     savedMessage = message; // args for deferred call
7661                     savedState = cps;
7662                     ScheduleDelayedEvent(DeferredBookMove, 10);
7663                     return;
7664                 }
7665                 goto FakeBookMove;
7666         }
7667
7668         return;
7669     }
7670
7671     /* Set special modes for chess engines.  Later something general
7672      *  could be added here; for now there is just one kludge feature,
7673      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7674      *  when "xboard" is given as an interactive command.
7675      */
7676     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7677         cps->useSigint = FALSE;
7678         cps->useSigterm = FALSE;
7679     }
7680     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7681       ParseFeatures(message+8, cps);
7682       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7683     }
7684
7685     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7686       int dummy, s=6; char buf[MSG_SIZ];
7687       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7688       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7689       ParseFEN(boards[0], &dummy, message+s);
7690       DrawPosition(TRUE, boards[0]);
7691       startedFromSetupPosition = TRUE;
7692       return;
7693     }
7694     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7695      * want this, I was asked to put it in, and obliged.
7696      */
7697     if (!strncmp(message, "setboard ", 9)) {
7698         Board initial_position;
7699
7700         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7701
7702         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7703             DisplayError(_("Bad FEN received from engine"), 0);
7704             return ;
7705         } else {
7706            Reset(TRUE, FALSE);
7707            CopyBoard(boards[0], initial_position);
7708            initialRulePlies = FENrulePlies;
7709            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7710            else gameMode = MachinePlaysBlack;
7711            DrawPosition(FALSE, boards[currentMove]);
7712         }
7713         return;
7714     }
7715
7716     /*
7717      * Look for communication commands
7718      */
7719     if (!strncmp(message, "telluser ", 9)) {
7720         if(message[9] == '\\' && message[10] == '\\')
7721             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7722         DisplayNote(message + 9);
7723         return;
7724     }
7725     if (!strncmp(message, "tellusererror ", 14)) {
7726         cps->userError = 1;
7727         if(message[14] == '\\' && message[15] == '\\')
7728             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7729         DisplayError(message + 14, 0);
7730         return;
7731     }
7732     if (!strncmp(message, "tellopponent ", 13)) {
7733       if (appData.icsActive) {
7734         if (loggedOn) {
7735           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7736           SendToICS(buf1);
7737         }
7738       } else {
7739         DisplayNote(message + 13);
7740       }
7741       return;
7742     }
7743     if (!strncmp(message, "tellothers ", 11)) {
7744       if (appData.icsActive) {
7745         if (loggedOn) {
7746           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7747           SendToICS(buf1);
7748         }
7749       }
7750       return;
7751     }
7752     if (!strncmp(message, "tellall ", 8)) {
7753       if (appData.icsActive) {
7754         if (loggedOn) {
7755           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7756           SendToICS(buf1);
7757         }
7758       } else {
7759         DisplayNote(message + 8);
7760       }
7761       return;
7762     }
7763     if (strncmp(message, "warning", 7) == 0) {
7764         /* Undocumented feature, use tellusererror in new code */
7765         DisplayError(message, 0);
7766         return;
7767     }
7768     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7769         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7770         strcat(realname, " query");
7771         AskQuestion(realname, buf2, buf1, cps->pr);
7772         return;
7773     }
7774     /* Commands from the engine directly to ICS.  We don't allow these to be
7775      *  sent until we are logged on. Crafty kibitzes have been known to
7776      *  interfere with the login process.
7777      */
7778     if (loggedOn) {
7779         if (!strncmp(message, "tellics ", 8)) {
7780             SendToICS(message + 8);
7781             SendToICS("\n");
7782             return;
7783         }
7784         if (!strncmp(message, "tellicsnoalias ", 15)) {
7785             SendToICS(ics_prefix);
7786             SendToICS(message + 15);
7787             SendToICS("\n");
7788             return;
7789         }
7790         /* The following are for backward compatibility only */
7791         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7792             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7793             SendToICS(ics_prefix);
7794             SendToICS(message);
7795             SendToICS("\n");
7796             return;
7797         }
7798     }
7799     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7800         return;
7801     }
7802     /*
7803      * If the move is illegal, cancel it and redraw the board.
7804      * Also deal with other error cases.  Matching is rather loose
7805      * here to accommodate engines written before the spec.
7806      */
7807     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7808         strncmp(message, "Error", 5) == 0) {
7809         if (StrStr(message, "name") ||
7810             StrStr(message, "rating") || StrStr(message, "?") ||
7811             StrStr(message, "result") || StrStr(message, "board") ||
7812             StrStr(message, "bk") || StrStr(message, "computer") ||
7813             StrStr(message, "variant") || StrStr(message, "hint") ||
7814             StrStr(message, "random") || StrStr(message, "depth") ||
7815             StrStr(message, "accepted")) {
7816             return;
7817         }
7818         if (StrStr(message, "protover")) {
7819           /* Program is responding to input, so it's apparently done
7820              initializing, and this error message indicates it is
7821              protocol version 1.  So we don't need to wait any longer
7822              for it to initialize and send feature commands. */
7823           FeatureDone(cps, 1);
7824           cps->protocolVersion = 1;
7825           return;
7826         }
7827         cps->maybeThinking = FALSE;
7828
7829         if (StrStr(message, "draw")) {
7830             /* Program doesn't have "draw" command */
7831             cps->sendDrawOffers = 0;
7832             return;
7833         }
7834         if (cps->sendTime != 1 &&
7835             (StrStr(message, "time") || StrStr(message, "otim"))) {
7836           /* Program apparently doesn't have "time" or "otim" command */
7837           cps->sendTime = 0;
7838           return;
7839         }
7840         if (StrStr(message, "analyze")) {
7841             cps->analysisSupport = FALSE;
7842             cps->analyzing = FALSE;
7843             Reset(FALSE, TRUE);
7844             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7845             DisplayError(buf2, 0);
7846             return;
7847         }
7848         if (StrStr(message, "(no matching move)st")) {
7849           /* Special kludge for GNU Chess 4 only */
7850           cps->stKludge = TRUE;
7851           SendTimeControl(cps, movesPerSession, timeControl,
7852                           timeIncrement, appData.searchDepth,
7853                           searchTime);
7854           return;
7855         }
7856         if (StrStr(message, "(no matching move)sd")) {
7857           /* Special kludge for GNU Chess 4 only */
7858           cps->sdKludge = TRUE;
7859           SendTimeControl(cps, movesPerSession, timeControl,
7860                           timeIncrement, appData.searchDepth,
7861                           searchTime);
7862           return;
7863         }
7864         if (!StrStr(message, "llegal")) {
7865             return;
7866         }
7867         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7868             gameMode == IcsIdle) return;
7869         if (forwardMostMove <= backwardMostMove) return;
7870         if (pausing) PauseEvent();
7871       if(appData.forceIllegal) {
7872             // [HGM] illegal: machine refused move; force position after move into it
7873           SendToProgram("force\n", cps);
7874           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7875                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7876                 // when black is to move, while there might be nothing on a2 or black
7877                 // might already have the move. So send the board as if white has the move.
7878                 // But first we must change the stm of the engine, as it refused the last move
7879                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7880                 if(WhiteOnMove(forwardMostMove)) {
7881                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7882                     SendBoard(cps, forwardMostMove); // kludgeless board
7883                 } else {
7884                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7885                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7886                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7887                 }
7888           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7889             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7890                  gameMode == TwoMachinesPlay)
7891               SendToProgram("go\n", cps);
7892             return;
7893       } else
7894         if (gameMode == PlayFromGameFile) {
7895             /* Stop reading this game file */
7896             gameMode = EditGame;
7897             ModeHighlight();
7898         }
7899         /* [HGM] illegal-move claim should forfeit game when Xboard */
7900         /* only passes fully legal moves                            */
7901         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7902             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7903                                 "False illegal-move claim", GE_XBOARD );
7904             return; // do not take back move we tested as valid
7905         }
7906         currentMove = forwardMostMove-1;
7907         DisplayMove(currentMove-1); /* before DisplayMoveError */
7908         SwitchClocks(forwardMostMove-1); // [HGM] race
7909         DisplayBothClocks();
7910         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7911                 parseList[currentMove], _(cps->which));
7912         DisplayMoveError(buf1);
7913         DrawPosition(FALSE, boards[currentMove]);
7914         return;
7915     }
7916     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7917         /* Program has a broken "time" command that
7918            outputs a string not ending in newline.
7919            Don't use it. */
7920         cps->sendTime = 0;
7921     }
7922
7923     /*
7924      * If chess program startup fails, exit with an error message.
7925      * Attempts to recover here are futile.
7926      */
7927     if ((StrStr(message, "unknown host") != NULL)
7928         || (StrStr(message, "No remote directory") != NULL)
7929         || (StrStr(message, "not found") != NULL)
7930         || (StrStr(message, "No such file") != NULL)
7931         || (StrStr(message, "can't alloc") != NULL)
7932         || (StrStr(message, "Permission denied") != NULL)) {
7933
7934         cps->maybeThinking = FALSE;
7935         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7936                 _(cps->which), cps->program, cps->host, message);
7937         RemoveInputSource(cps->isr);
7938         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7939             if(cps == &first) appData.noChessProgram = TRUE;
7940             DisplayError(buf1, 0);
7941         }
7942         return;
7943     }
7944
7945     /*
7946      * Look for hint output
7947      */
7948     if (sscanf(message, "Hint: %s", buf1) == 1) {
7949         if (cps == &first && hintRequested) {
7950             hintRequested = FALSE;
7951             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7952                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7953                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7954                                     PosFlags(forwardMostMove),
7955                                     fromY, fromX, toY, toX, promoChar, buf1);
7956                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7957                 DisplayInformation(buf2);
7958             } else {
7959                 /* Hint move could not be parsed!? */
7960               snprintf(buf2, sizeof(buf2),
7961                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7962                         buf1, _(cps->which));
7963                 DisplayError(buf2, 0);
7964             }
7965         } else {
7966           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7967         }
7968         return;
7969     }
7970
7971     /*
7972      * Ignore other messages if game is not in progress
7973      */
7974     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7975         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7976
7977     /*
7978      * look for win, lose, draw, or draw offer
7979      */
7980     if (strncmp(message, "1-0", 3) == 0) {
7981         char *p, *q, *r = "";
7982         p = strchr(message, '{');
7983         if (p) {
7984             q = strchr(p, '}');
7985             if (q) {
7986                 *q = NULLCHAR;
7987                 r = p + 1;
7988             }
7989         }
7990         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7991         return;
7992     } else if (strncmp(message, "0-1", 3) == 0) {
7993         char *p, *q, *r = "";
7994         p = strchr(message, '{');
7995         if (p) {
7996             q = strchr(p, '}');
7997             if (q) {
7998                 *q = NULLCHAR;
7999                 r = p + 1;
8000             }
8001         }
8002         /* Kludge for Arasan 4.1 bug */
8003         if (strcmp(r, "Black resigns") == 0) {
8004             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8005             return;
8006         }
8007         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8008         return;
8009     } else if (strncmp(message, "1/2", 3) == 0) {
8010         char *p, *q, *r = "";
8011         p = strchr(message, '{');
8012         if (p) {
8013             q = strchr(p, '}');
8014             if (q) {
8015                 *q = NULLCHAR;
8016                 r = p + 1;
8017             }
8018         }
8019
8020         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8021         return;
8022
8023     } else if (strncmp(message, "White resign", 12) == 0) {
8024         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8025         return;
8026     } else if (strncmp(message, "Black resign", 12) == 0) {
8027         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8028         return;
8029     } else if (strncmp(message, "White matches", 13) == 0 ||
8030                strncmp(message, "Black matches", 13) == 0   ) {
8031         /* [HGM] ignore GNUShogi noises */
8032         return;
8033     } else if (strncmp(message, "White", 5) == 0 &&
8034                message[5] != '(' &&
8035                StrStr(message, "Black") == NULL) {
8036         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8037         return;
8038     } else if (strncmp(message, "Black", 5) == 0 &&
8039                message[5] != '(') {
8040         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8041         return;
8042     } else if (strcmp(message, "resign") == 0 ||
8043                strcmp(message, "computer resigns") == 0) {
8044         switch (gameMode) {
8045           case MachinePlaysBlack:
8046           case IcsPlayingBlack:
8047             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8048             break;
8049           case MachinePlaysWhite:
8050           case IcsPlayingWhite:
8051             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8052             break;
8053           case TwoMachinesPlay:
8054             if (cps->twoMachinesColor[0] == 'w')
8055               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8056             else
8057               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8058             break;
8059           default:
8060             /* can't happen */
8061             break;
8062         }
8063         return;
8064     } else if (strncmp(message, "opponent mates", 14) == 0) {
8065         switch (gameMode) {
8066           case MachinePlaysBlack:
8067           case IcsPlayingBlack:
8068             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8069             break;
8070           case MachinePlaysWhite:
8071           case IcsPlayingWhite:
8072             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8073             break;
8074           case TwoMachinesPlay:
8075             if (cps->twoMachinesColor[0] == 'w')
8076               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8077             else
8078               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8079             break;
8080           default:
8081             /* can't happen */
8082             break;
8083         }
8084         return;
8085     } else if (strncmp(message, "computer mates", 14) == 0) {
8086         switch (gameMode) {
8087           case MachinePlaysBlack:
8088           case IcsPlayingBlack:
8089             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8090             break;
8091           case MachinePlaysWhite:
8092           case IcsPlayingWhite:
8093             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8094             break;
8095           case TwoMachinesPlay:
8096             if (cps->twoMachinesColor[0] == 'w')
8097               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8098             else
8099               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8100             break;
8101           default:
8102             /* can't happen */
8103             break;
8104         }
8105         return;
8106     } else if (strncmp(message, "checkmate", 9) == 0) {
8107         if (WhiteOnMove(forwardMostMove)) {
8108             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8109         } else {
8110             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8111         }
8112         return;
8113     } else if (strstr(message, "Draw") != NULL ||
8114                strstr(message, "game is a draw") != NULL) {
8115         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8116         return;
8117     } else if (strstr(message, "offer") != NULL &&
8118                strstr(message, "draw") != NULL) {
8119 #if ZIPPY
8120         if (appData.zippyPlay && first.initDone) {
8121             /* Relay offer to ICS */
8122             SendToICS(ics_prefix);
8123             SendToICS("draw\n");
8124         }
8125 #endif
8126         cps->offeredDraw = 2; /* valid until this engine moves twice */
8127         if (gameMode == TwoMachinesPlay) {
8128             if (cps->other->offeredDraw) {
8129                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8130             /* [HGM] in two-machine mode we delay relaying draw offer      */
8131             /* until after we also have move, to see if it is really claim */
8132             }
8133         } else if (gameMode == MachinePlaysWhite ||
8134                    gameMode == MachinePlaysBlack) {
8135           if (userOfferedDraw) {
8136             DisplayInformation(_("Machine accepts your draw offer"));
8137             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8138           } else {
8139             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8140           }
8141         }
8142     }
8143
8144
8145     /*
8146      * Look for thinking output
8147      */
8148     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8149           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8150                                 ) {
8151         int plylev, mvleft, mvtot, curscore, time;
8152         char mvname[MOVE_LEN];
8153         u64 nodes; // [DM]
8154         char plyext;
8155         int ignore = FALSE;
8156         int prefixHint = FALSE;
8157         mvname[0] = NULLCHAR;
8158
8159         switch (gameMode) {
8160           case MachinePlaysBlack:
8161           case IcsPlayingBlack:
8162             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8163             break;
8164           case MachinePlaysWhite:
8165           case IcsPlayingWhite:
8166             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8167             break;
8168           case AnalyzeMode:
8169           case AnalyzeFile:
8170             break;
8171           case IcsObserving: /* [DM] icsEngineAnalyze */
8172             if (!appData.icsEngineAnalyze) ignore = TRUE;
8173             break;
8174           case TwoMachinesPlay:
8175             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8176                 ignore = TRUE;
8177             }
8178             break;
8179           default:
8180             ignore = TRUE;
8181             break;
8182         }
8183
8184         if (!ignore) {
8185             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8186             buf1[0] = NULLCHAR;
8187             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8188                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8189
8190                 if (plyext != ' ' && plyext != '\t') {
8191                     time *= 100;
8192                 }
8193
8194                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8195                 if( cps->scoreIsAbsolute &&
8196                     ( gameMode == MachinePlaysBlack ||
8197                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8198                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8199                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8200                      !WhiteOnMove(currentMove)
8201                     ) )
8202                 {
8203                     curscore = -curscore;
8204                 }
8205
8206
8207                 tempStats.depth = plylev;
8208                 tempStats.nodes = nodes;
8209                 tempStats.time = time;
8210                 tempStats.score = curscore;
8211                 tempStats.got_only_move = 0;
8212
8213                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8214                         int ticklen;
8215
8216                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8217                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8218                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8219                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8220                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8221                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8222                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8223                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8224                 }
8225
8226                 /* Buffer overflow protection */
8227                 if (buf1[0] != NULLCHAR) {
8228                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8229                         && appData.debugMode) {
8230                         fprintf(debugFP,
8231                                 "PV is too long; using the first %u bytes.\n",
8232                                 (unsigned) sizeof(tempStats.movelist) - 1);
8233                     }
8234
8235                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8236                 } else {
8237                     sprintf(tempStats.movelist, " no PV\n");
8238                 }
8239
8240                 if (tempStats.seen_stat) {
8241                     tempStats.ok_to_send = 1;
8242                 }
8243
8244                 if (strchr(tempStats.movelist, '(') != NULL) {
8245                     tempStats.line_is_book = 1;
8246                     tempStats.nr_moves = 0;
8247                     tempStats.moves_left = 0;
8248                 } else {
8249                     tempStats.line_is_book = 0;
8250                 }
8251
8252                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8253                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8254
8255                 SendProgramStatsToFrontend( cps, &tempStats );
8256
8257                 /*
8258                     [AS] Protect the thinkOutput buffer from overflow... this
8259                     is only useful if buf1 hasn't overflowed first!
8260                 */
8261                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8262                          plylev,
8263                          (gameMode == TwoMachinesPlay ?
8264                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8265                          ((double) curscore) / 100.0,
8266                          prefixHint ? lastHint : "",
8267                          prefixHint ? " " : "" );
8268
8269                 if( buf1[0] != NULLCHAR ) {
8270                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8271
8272                     if( strlen(buf1) > max_len ) {
8273                         if( appData.debugMode) {
8274                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8275                         }
8276                         buf1[max_len+1] = '\0';
8277                     }
8278
8279                     strcat( thinkOutput, buf1 );
8280                 }
8281
8282                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8283                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8284                     DisplayMove(currentMove - 1);
8285                 }
8286                 return;
8287
8288             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8289                 /* crafty (9.25+) says "(only move) <move>"
8290                  * if there is only 1 legal move
8291                  */
8292                 sscanf(p, "(only move) %s", buf1);
8293                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8294                 sprintf(programStats.movelist, "%s (only move)", buf1);
8295                 programStats.depth = 1;
8296                 programStats.nr_moves = 1;
8297                 programStats.moves_left = 1;
8298                 programStats.nodes = 1;
8299                 programStats.time = 1;
8300                 programStats.got_only_move = 1;
8301
8302                 /* Not really, but we also use this member to
8303                    mean "line isn't going to change" (Crafty
8304                    isn't searching, so stats won't change) */
8305                 programStats.line_is_book = 1;
8306
8307                 SendProgramStatsToFrontend( cps, &programStats );
8308
8309                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8310                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8311                     DisplayMove(currentMove - 1);
8312                 }
8313                 return;
8314             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8315                               &time, &nodes, &plylev, &mvleft,
8316                               &mvtot, mvname) >= 5) {
8317                 /* The stat01: line is from Crafty (9.29+) in response
8318                    to the "." command */
8319                 programStats.seen_stat = 1;
8320                 cps->maybeThinking = TRUE;
8321
8322                 if (programStats.got_only_move || !appData.periodicUpdates)
8323                   return;
8324
8325                 programStats.depth = plylev;
8326                 programStats.time = time;
8327                 programStats.nodes = nodes;
8328                 programStats.moves_left = mvleft;
8329                 programStats.nr_moves = mvtot;
8330                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8331                 programStats.ok_to_send = 1;
8332                 programStats.movelist[0] = '\0';
8333
8334                 SendProgramStatsToFrontend( cps, &programStats );
8335
8336                 return;
8337
8338             } else if (strncmp(message,"++",2) == 0) {
8339                 /* Crafty 9.29+ outputs this */
8340                 programStats.got_fail = 2;
8341                 return;
8342
8343             } else if (strncmp(message,"--",2) == 0) {
8344                 /* Crafty 9.29+ outputs this */
8345                 programStats.got_fail = 1;
8346                 return;
8347
8348             } else if (thinkOutput[0] != NULLCHAR &&
8349                        strncmp(message, "    ", 4) == 0) {
8350                 unsigned message_len;
8351
8352                 p = message;
8353                 while (*p && *p == ' ') p++;
8354
8355                 message_len = strlen( p );
8356
8357                 /* [AS] Avoid buffer overflow */
8358                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8359                     strcat(thinkOutput, " ");
8360                     strcat(thinkOutput, p);
8361                 }
8362
8363                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8364                     strcat(programStats.movelist, " ");
8365                     strcat(programStats.movelist, p);
8366                 }
8367
8368                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8369                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8370                     DisplayMove(currentMove - 1);
8371                 }
8372                 return;
8373             }
8374         }
8375         else {
8376             buf1[0] = NULLCHAR;
8377
8378             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8379                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8380             {
8381                 ChessProgramStats cpstats;
8382
8383                 if (plyext != ' ' && plyext != '\t') {
8384                     time *= 100;
8385                 }
8386
8387                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8388                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8389                     curscore = -curscore;
8390                 }
8391
8392                 cpstats.depth = plylev;
8393                 cpstats.nodes = nodes;
8394                 cpstats.time = time;
8395                 cpstats.score = curscore;
8396                 cpstats.got_only_move = 0;
8397                 cpstats.movelist[0] = '\0';
8398
8399                 if (buf1[0] != NULLCHAR) {
8400                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8401                 }
8402
8403                 cpstats.ok_to_send = 0;
8404                 cpstats.line_is_book = 0;
8405                 cpstats.nr_moves = 0;
8406                 cpstats.moves_left = 0;
8407
8408                 SendProgramStatsToFrontend( cps, &cpstats );
8409             }
8410         }
8411     }
8412 }
8413
8414
8415 /* Parse a game score from the character string "game", and
8416    record it as the history of the current game.  The game
8417    score is NOT assumed to start from the standard position.
8418    The display is not updated in any way.
8419    */
8420 void
8421 ParseGameHistory(game)
8422      char *game;
8423 {
8424     ChessMove moveType;
8425     int fromX, fromY, toX, toY, boardIndex;
8426     char promoChar;
8427     char *p, *q;
8428     char buf[MSG_SIZ];
8429
8430     if (appData.debugMode)
8431       fprintf(debugFP, "Parsing game history: %s\n", game);
8432
8433     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8434     gameInfo.site = StrSave(appData.icsHost);
8435     gameInfo.date = PGNDate();
8436     gameInfo.round = StrSave("-");
8437
8438     /* Parse out names of players */
8439     while (*game == ' ') game++;
8440     p = buf;
8441     while (*game != ' ') *p++ = *game++;
8442     *p = NULLCHAR;
8443     gameInfo.white = StrSave(buf);
8444     while (*game == ' ') game++;
8445     p = buf;
8446     while (*game != ' ' && *game != '\n') *p++ = *game++;
8447     *p = NULLCHAR;
8448     gameInfo.black = StrSave(buf);
8449
8450     /* Parse moves */
8451     boardIndex = blackPlaysFirst ? 1 : 0;
8452     yynewstr(game);
8453     for (;;) {
8454         yyboardindex = boardIndex;
8455         moveType = (ChessMove) Myylex();
8456         switch (moveType) {
8457           case IllegalMove:             /* maybe suicide chess, etc. */
8458   if (appData.debugMode) {
8459     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8460     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8461     setbuf(debugFP, NULL);
8462   }
8463           case WhitePromotion:
8464           case BlackPromotion:
8465           case WhiteNonPromotion:
8466           case BlackNonPromotion:
8467           case NormalMove:
8468           case WhiteCapturesEnPassant:
8469           case BlackCapturesEnPassant:
8470           case WhiteKingSideCastle:
8471           case WhiteQueenSideCastle:
8472           case BlackKingSideCastle:
8473           case BlackQueenSideCastle:
8474           case WhiteKingSideCastleWild:
8475           case WhiteQueenSideCastleWild:
8476           case BlackKingSideCastleWild:
8477           case BlackQueenSideCastleWild:
8478           /* PUSH Fabien */
8479           case WhiteHSideCastleFR:
8480           case WhiteASideCastleFR:
8481           case BlackHSideCastleFR:
8482           case BlackASideCastleFR:
8483           /* POP Fabien */
8484             fromX = currentMoveString[0] - AAA;
8485             fromY = currentMoveString[1] - ONE;
8486             toX = currentMoveString[2] - AAA;
8487             toY = currentMoveString[3] - ONE;
8488             promoChar = currentMoveString[4];
8489             break;
8490           case WhiteDrop:
8491           case BlackDrop:
8492             fromX = moveType == WhiteDrop ?
8493               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8494             (int) CharToPiece(ToLower(currentMoveString[0]));
8495             fromY = DROP_RANK;
8496             toX = currentMoveString[2] - AAA;
8497             toY = currentMoveString[3] - ONE;
8498             promoChar = NULLCHAR;
8499             break;
8500           case AmbiguousMove:
8501             /* bug? */
8502             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8503   if (appData.debugMode) {
8504     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8505     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8506     setbuf(debugFP, NULL);
8507   }
8508             DisplayError(buf, 0);
8509             return;
8510           case ImpossibleMove:
8511             /* bug? */
8512             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8513   if (appData.debugMode) {
8514     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8515     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8516     setbuf(debugFP, NULL);
8517   }
8518             DisplayError(buf, 0);
8519             return;
8520           case EndOfFile:
8521             if (boardIndex < backwardMostMove) {
8522                 /* Oops, gap.  How did that happen? */
8523                 DisplayError(_("Gap in move list"), 0);
8524                 return;
8525             }
8526             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8527             if (boardIndex > forwardMostMove) {
8528                 forwardMostMove = boardIndex;
8529             }
8530             return;
8531           case ElapsedTime:
8532             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8533                 strcat(parseList[boardIndex-1], " ");
8534                 strcat(parseList[boardIndex-1], yy_text);
8535             }
8536             continue;
8537           case Comment:
8538           case PGNTag:
8539           case NAG:
8540           default:
8541             /* ignore */
8542             continue;
8543           case WhiteWins:
8544           case BlackWins:
8545           case GameIsDrawn:
8546           case GameUnfinished:
8547             if (gameMode == IcsExamining) {
8548                 if (boardIndex < backwardMostMove) {
8549                     /* Oops, gap.  How did that happen? */
8550                     return;
8551                 }
8552                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8553                 return;
8554             }
8555             gameInfo.result = moveType;
8556             p = strchr(yy_text, '{');
8557             if (p == NULL) p = strchr(yy_text, '(');
8558             if (p == NULL) {
8559                 p = yy_text;
8560                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8561             } else {
8562                 q = strchr(p, *p == '{' ? '}' : ')');
8563                 if (q != NULL) *q = NULLCHAR;
8564                 p++;
8565             }
8566             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8567             gameInfo.resultDetails = StrSave(p);
8568             continue;
8569         }
8570         if (boardIndex >= forwardMostMove &&
8571             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8572             backwardMostMove = blackPlaysFirst ? 1 : 0;
8573             return;
8574         }
8575         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8576                                  fromY, fromX, toY, toX, promoChar,
8577                                  parseList[boardIndex]);
8578         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8579         /* currentMoveString is set as a side-effect of yylex */
8580         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8581         strcat(moveList[boardIndex], "\n");
8582         boardIndex++;
8583         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8584         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8585           case MT_NONE:
8586           case MT_STALEMATE:
8587           default:
8588             break;
8589           case MT_CHECK:
8590             if(gameInfo.variant != VariantShogi)
8591                 strcat(parseList[boardIndex - 1], "+");
8592             break;
8593           case MT_CHECKMATE:
8594           case MT_STAINMATE:
8595             strcat(parseList[boardIndex - 1], "#");
8596             break;
8597         }
8598     }
8599 }
8600
8601
8602 /* Apply a move to the given board  */
8603 void
8604 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8605      int fromX, fromY, toX, toY;
8606      int promoChar;
8607      Board board;
8608 {
8609   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8610   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8611
8612     /* [HGM] compute & store e.p. status and castling rights for new position */
8613     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8614
8615       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8616       oldEP = (signed char)board[EP_STATUS];
8617       board[EP_STATUS] = EP_NONE;
8618
8619       if( board[toY][toX] != EmptySquare )
8620            board[EP_STATUS] = EP_CAPTURE;
8621
8622   if (fromY == DROP_RANK) {
8623         /* must be first */
8624         piece = board[toY][toX] = (ChessSquare) fromX;
8625   } else {
8626       int i;
8627
8628       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8629            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8630                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8631       } else
8632       if( board[fromY][fromX] == WhitePawn ) {
8633            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8634                board[EP_STATUS] = EP_PAWN_MOVE;
8635            if( toY-fromY==2) {
8636                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8637                         gameInfo.variant != VariantBerolina || toX < fromX)
8638                       board[EP_STATUS] = toX | berolina;
8639                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8640                         gameInfo.variant != VariantBerolina || toX > fromX)
8641                       board[EP_STATUS] = toX;
8642            }
8643       } else
8644       if( board[fromY][fromX] == BlackPawn ) {
8645            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8646                board[EP_STATUS] = EP_PAWN_MOVE;
8647            if( toY-fromY== -2) {
8648                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8649                         gameInfo.variant != VariantBerolina || toX < fromX)
8650                       board[EP_STATUS] = toX | berolina;
8651                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8652                         gameInfo.variant != VariantBerolina || toX > fromX)
8653                       board[EP_STATUS] = toX;
8654            }
8655        }
8656
8657        for(i=0; i<nrCastlingRights; i++) {
8658            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8659               board[CASTLING][i] == toX   && castlingRank[i] == toY
8660              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8661        }
8662
8663      if (fromX == toX && fromY == toY) return;
8664
8665      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8666      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8667      if(gameInfo.variant == VariantKnightmate)
8668          king += (int) WhiteUnicorn - (int) WhiteKing;
8669
8670     /* Code added by Tord: */
8671     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8672     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8673         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8674       board[fromY][fromX] = EmptySquare;
8675       board[toY][toX] = EmptySquare;
8676       if((toX > fromX) != (piece == WhiteRook)) {
8677         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8678       } else {
8679         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8680       }
8681     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8682                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8683       board[fromY][fromX] = EmptySquare;
8684       board[toY][toX] = EmptySquare;
8685       if((toX > fromX) != (piece == BlackRook)) {
8686         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8687       } else {
8688         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8689       }
8690     /* End of code added by Tord */
8691
8692     } else if (board[fromY][fromX] == king
8693         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8694         && toY == fromY && toX > fromX+1) {
8695         board[fromY][fromX] = EmptySquare;
8696         board[toY][toX] = king;
8697         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8698         board[fromY][BOARD_RGHT-1] = EmptySquare;
8699     } else if (board[fromY][fromX] == king
8700         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8701                && toY == fromY && toX < fromX-1) {
8702         board[fromY][fromX] = EmptySquare;
8703         board[toY][toX] = king;
8704         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8705         board[fromY][BOARD_LEFT] = EmptySquare;
8706     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8707                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8708                && toY >= BOARD_HEIGHT-promoRank
8709                ) {
8710         /* white pawn promotion */
8711         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8712         if (board[toY][toX] == EmptySquare) {
8713             board[toY][toX] = WhiteQueen;
8714         }
8715         if(gameInfo.variant==VariantBughouse ||
8716            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8717             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8718         board[fromY][fromX] = EmptySquare;
8719     } else if ((fromY == BOARD_HEIGHT-4)
8720                && (toX != fromX)
8721                && gameInfo.variant != VariantXiangqi
8722                && gameInfo.variant != VariantBerolina
8723                && (board[fromY][fromX] == WhitePawn)
8724                && (board[toY][toX] == EmptySquare)) {
8725         board[fromY][fromX] = EmptySquare;
8726         board[toY][toX] = WhitePawn;
8727         captured = board[toY - 1][toX];
8728         board[toY - 1][toX] = EmptySquare;
8729     } else if ((fromY == BOARD_HEIGHT-4)
8730                && (toX == fromX)
8731                && gameInfo.variant == VariantBerolina
8732                && (board[fromY][fromX] == WhitePawn)
8733                && (board[toY][toX] == EmptySquare)) {
8734         board[fromY][fromX] = EmptySquare;
8735         board[toY][toX] = WhitePawn;
8736         if(oldEP & EP_BEROLIN_A) {
8737                 captured = board[fromY][fromX-1];
8738                 board[fromY][fromX-1] = EmptySquare;
8739         }else{  captured = board[fromY][fromX+1];
8740                 board[fromY][fromX+1] = EmptySquare;
8741         }
8742     } else if (board[fromY][fromX] == king
8743         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8744                && toY == fromY && toX > fromX+1) {
8745         board[fromY][fromX] = EmptySquare;
8746         board[toY][toX] = king;
8747         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8748         board[fromY][BOARD_RGHT-1] = EmptySquare;
8749     } else if (board[fromY][fromX] == king
8750         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8751                && toY == fromY && toX < fromX-1) {
8752         board[fromY][fromX] = EmptySquare;
8753         board[toY][toX] = king;
8754         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8755         board[fromY][BOARD_LEFT] = EmptySquare;
8756     } else if (fromY == 7 && fromX == 3
8757                && board[fromY][fromX] == BlackKing
8758                && toY == 7 && toX == 5) {
8759         board[fromY][fromX] = EmptySquare;
8760         board[toY][toX] = BlackKing;
8761         board[fromY][7] = EmptySquare;
8762         board[toY][4] = BlackRook;
8763     } else if (fromY == 7 && fromX == 3
8764                && board[fromY][fromX] == BlackKing
8765                && toY == 7 && toX == 1) {
8766         board[fromY][fromX] = EmptySquare;
8767         board[toY][toX] = BlackKing;
8768         board[fromY][0] = EmptySquare;
8769         board[toY][2] = BlackRook;
8770     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8771                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8772                && toY < promoRank
8773                ) {
8774         /* black pawn promotion */
8775         board[toY][toX] = CharToPiece(ToLower(promoChar));
8776         if (board[toY][toX] == EmptySquare) {
8777             board[toY][toX] = BlackQueen;
8778         }
8779         if(gameInfo.variant==VariantBughouse ||
8780            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8781             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8782         board[fromY][fromX] = EmptySquare;
8783     } else if ((fromY == 3)
8784                && (toX != fromX)
8785                && gameInfo.variant != VariantXiangqi
8786                && gameInfo.variant != VariantBerolina
8787                && (board[fromY][fromX] == BlackPawn)
8788                && (board[toY][toX] == EmptySquare)) {
8789         board[fromY][fromX] = EmptySquare;
8790         board[toY][toX] = BlackPawn;
8791         captured = board[toY + 1][toX];
8792         board[toY + 1][toX] = EmptySquare;
8793     } else if ((fromY == 3)
8794                && (toX == fromX)
8795                && gameInfo.variant == VariantBerolina
8796                && (board[fromY][fromX] == BlackPawn)
8797                && (board[toY][toX] == EmptySquare)) {
8798         board[fromY][fromX] = EmptySquare;
8799         board[toY][toX] = BlackPawn;
8800         if(oldEP & EP_BEROLIN_A) {
8801                 captured = board[fromY][fromX-1];
8802                 board[fromY][fromX-1] = EmptySquare;
8803         }else{  captured = board[fromY][fromX+1];
8804                 board[fromY][fromX+1] = EmptySquare;
8805         }
8806     } else {
8807         board[toY][toX] = board[fromY][fromX];
8808         board[fromY][fromX] = EmptySquare;
8809     }
8810   }
8811
8812     if (gameInfo.holdingsWidth != 0) {
8813
8814       /* !!A lot more code needs to be written to support holdings  */
8815       /* [HGM] OK, so I have written it. Holdings are stored in the */
8816       /* penultimate board files, so they are automaticlly stored   */
8817       /* in the game history.                                       */
8818       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8819                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8820         /* Delete from holdings, by decreasing count */
8821         /* and erasing image if necessary            */
8822         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8823         if(p < (int) BlackPawn) { /* white drop */
8824              p -= (int)WhitePawn;
8825                  p = PieceToNumber((ChessSquare)p);
8826              if(p >= gameInfo.holdingsSize) p = 0;
8827              if(--board[p][BOARD_WIDTH-2] <= 0)
8828                   board[p][BOARD_WIDTH-1] = EmptySquare;
8829              if((int)board[p][BOARD_WIDTH-2] < 0)
8830                         board[p][BOARD_WIDTH-2] = 0;
8831         } else {                  /* black drop */
8832              p -= (int)BlackPawn;
8833                  p = PieceToNumber((ChessSquare)p);
8834              if(p >= gameInfo.holdingsSize) p = 0;
8835              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8836                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8837              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8838                         board[BOARD_HEIGHT-1-p][1] = 0;
8839         }
8840       }
8841       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8842           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8843         /* [HGM] holdings: Add to holdings, if holdings exist */
8844         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8845                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8846                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8847         }
8848         p = (int) captured;
8849         if (p >= (int) BlackPawn) {
8850           p -= (int)BlackPawn;
8851           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8852                   /* in Shogi restore piece to its original  first */
8853                   captured = (ChessSquare) (DEMOTED captured);
8854                   p = DEMOTED p;
8855           }
8856           p = PieceToNumber((ChessSquare)p);
8857           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8858           board[p][BOARD_WIDTH-2]++;
8859           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8860         } else {
8861           p -= (int)WhitePawn;
8862           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8863                   captured = (ChessSquare) (DEMOTED captured);
8864                   p = DEMOTED p;
8865           }
8866           p = PieceToNumber((ChessSquare)p);
8867           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8868           board[BOARD_HEIGHT-1-p][1]++;
8869           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8870         }
8871       }
8872     } else if (gameInfo.variant == VariantAtomic) {
8873       if (captured != EmptySquare) {
8874         int y, x;
8875         for (y = toY-1; y <= toY+1; y++) {
8876           for (x = toX-1; x <= toX+1; x++) {
8877             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8878                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8879               board[y][x] = EmptySquare;
8880             }
8881           }
8882         }
8883         board[toY][toX] = EmptySquare;
8884       }
8885     }
8886     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8887         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8888     } else
8889     if(promoChar == '+') {
8890         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8891         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8892     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8893         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8894     }
8895     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8896                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8897         // [HGM] superchess: take promotion piece out of holdings
8898         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8899         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8900             if(!--board[k][BOARD_WIDTH-2])
8901                 board[k][BOARD_WIDTH-1] = EmptySquare;
8902         } else {
8903             if(!--board[BOARD_HEIGHT-1-k][1])
8904                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8905         }
8906     }
8907
8908 }
8909
8910 /* Updates forwardMostMove */
8911 void
8912 MakeMove(fromX, fromY, toX, toY, promoChar)
8913      int fromX, fromY, toX, toY;
8914      int promoChar;
8915 {
8916 //    forwardMostMove++; // [HGM] bare: moved downstream
8917
8918     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8919         int timeLeft; static int lastLoadFlag=0; int king, piece;
8920         piece = boards[forwardMostMove][fromY][fromX];
8921         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8922         if(gameInfo.variant == VariantKnightmate)
8923             king += (int) WhiteUnicorn - (int) WhiteKing;
8924         if(forwardMostMove == 0) {
8925             if(blackPlaysFirst)
8926                 fprintf(serverMoves, "%s;", second.tidy);
8927             fprintf(serverMoves, "%s;", first.tidy);
8928             if(!blackPlaysFirst)
8929                 fprintf(serverMoves, "%s;", second.tidy);
8930         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8931         lastLoadFlag = loadFlag;
8932         // print base move
8933         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8934         // print castling suffix
8935         if( toY == fromY && piece == king ) {
8936             if(toX-fromX > 1)
8937                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8938             if(fromX-toX >1)
8939                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8940         }
8941         // e.p. suffix
8942         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8943              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8944              boards[forwardMostMove][toY][toX] == EmptySquare
8945              && fromX != toX && fromY != toY)
8946                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8947         // promotion suffix
8948         if(promoChar != NULLCHAR)
8949                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8950         if(!loadFlag) {
8951             fprintf(serverMoves, "/%d/%d",
8952                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8953             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8954             else                      timeLeft = blackTimeRemaining/1000;
8955             fprintf(serverMoves, "/%d", timeLeft);
8956         }
8957         fflush(serverMoves);
8958     }
8959
8960     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8961       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8962                         0, 1);
8963       return;
8964     }
8965     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8966     if (commentList[forwardMostMove+1] != NULL) {
8967         free(commentList[forwardMostMove+1]);
8968         commentList[forwardMostMove+1] = NULL;
8969     }
8970     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8971     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8972     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8973     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8974     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8975     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8976     gameInfo.result = GameUnfinished;
8977     if (gameInfo.resultDetails != NULL) {
8978         free(gameInfo.resultDetails);
8979         gameInfo.resultDetails = NULL;
8980     }
8981     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8982                               moveList[forwardMostMove - 1]);
8983     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8984                              PosFlags(forwardMostMove - 1),
8985                              fromY, fromX, toY, toX, promoChar,
8986                              parseList[forwardMostMove - 1]);
8987     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8988       case MT_NONE:
8989       case MT_STALEMATE:
8990       default:
8991         break;
8992       case MT_CHECK:
8993         if(gameInfo.variant != VariantShogi)
8994             strcat(parseList[forwardMostMove - 1], "+");
8995         break;
8996       case MT_CHECKMATE:
8997       case MT_STAINMATE:
8998         strcat(parseList[forwardMostMove - 1], "#");
8999         break;
9000     }
9001     if (appData.debugMode) {
9002         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9003     }
9004
9005 }
9006
9007 /* Updates currentMove if not pausing */
9008 void
9009 ShowMove(fromX, fromY, toX, toY)
9010 {
9011     int instant = (gameMode == PlayFromGameFile) ?
9012         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9013     if(appData.noGUI) return;
9014     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9015         if (!instant) {
9016             if (forwardMostMove == currentMove + 1) {
9017                 AnimateMove(boards[forwardMostMove - 1],
9018                             fromX, fromY, toX, toY);
9019             }
9020             if (appData.highlightLastMove) {
9021                 SetHighlights(fromX, fromY, toX, toY);
9022             }
9023         }
9024         currentMove = forwardMostMove;
9025     }
9026
9027     if (instant) return;
9028
9029     DisplayMove(currentMove - 1);
9030     DrawPosition(FALSE, boards[currentMove]);
9031     DisplayBothClocks();
9032     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9033 }
9034
9035 void SendEgtPath(ChessProgramState *cps)
9036 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9037         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9038
9039         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9040
9041         while(*p) {
9042             char c, *q = name+1, *r, *s;
9043
9044             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9045             while(*p && *p != ',') *q++ = *p++;
9046             *q++ = ':'; *q = 0;
9047             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9048                 strcmp(name, ",nalimov:") == 0 ) {
9049                 // take nalimov path from the menu-changeable option first, if it is defined
9050               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9051                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9052             } else
9053             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9054                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9055                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9056                 s = r = StrStr(s, ":") + 1; // beginning of path info
9057                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9058                 c = *r; *r = 0;             // temporarily null-terminate path info
9059                     *--q = 0;               // strip of trailig ':' from name
9060                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9061                 *r = c;
9062                 SendToProgram(buf,cps);     // send egtbpath command for this format
9063             }
9064             if(*p == ',') p++; // read away comma to position for next format name
9065         }
9066 }
9067
9068 void
9069 InitChessProgram(cps, setup)
9070      ChessProgramState *cps;
9071      int setup; /* [HGM] needed to setup FRC opening position */
9072 {
9073     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9074     if (appData.noChessProgram) return;
9075     hintRequested = FALSE;
9076     bookRequested = FALSE;
9077
9078     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9079     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9080     if(cps->memSize) { /* [HGM] memory */
9081       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9082         SendToProgram(buf, cps);
9083     }
9084     SendEgtPath(cps); /* [HGM] EGT */
9085     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9086       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9087         SendToProgram(buf, cps);
9088     }
9089
9090     SendToProgram(cps->initString, cps);
9091     if (gameInfo.variant != VariantNormal &&
9092         gameInfo.variant != VariantLoadable
9093         /* [HGM] also send variant if board size non-standard */
9094         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9095                                             ) {
9096       char *v = VariantName(gameInfo.variant);
9097       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9098         /* [HGM] in protocol 1 we have to assume all variants valid */
9099         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9100         DisplayFatalError(buf, 0, 1);
9101         return;
9102       }
9103
9104       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9105       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9106       if( gameInfo.variant == VariantXiangqi )
9107            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9108       if( gameInfo.variant == VariantShogi )
9109            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9110       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9111            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9112       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9113           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9114            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9115       if( gameInfo.variant == VariantCourier )
9116            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9117       if( gameInfo.variant == VariantSuper )
9118            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9119       if( gameInfo.variant == VariantGreat )
9120            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9121       if( gameInfo.variant == VariantSChess )
9122            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9123
9124       if(overruled) {
9125         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9126                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9127            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9128            if(StrStr(cps->variants, b) == NULL) {
9129                // specific sized variant not known, check if general sizing allowed
9130                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9131                    if(StrStr(cps->variants, "boardsize") == NULL) {
9132                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9133                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9134                        DisplayFatalError(buf, 0, 1);
9135                        return;
9136                    }
9137                    /* [HGM] here we really should compare with the maximum supported board size */
9138                }
9139            }
9140       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9141       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9142       SendToProgram(buf, cps);
9143     }
9144     currentlyInitializedVariant = gameInfo.variant;
9145
9146     /* [HGM] send opening position in FRC to first engine */
9147     if(setup) {
9148           SendToProgram("force\n", cps);
9149           SendBoard(cps, 0);
9150           /* engine is now in force mode! Set flag to wake it up after first move. */
9151           setboardSpoiledMachineBlack = 1;
9152     }
9153
9154     if (cps->sendICS) {
9155       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9156       SendToProgram(buf, cps);
9157     }
9158     cps->maybeThinking = FALSE;
9159     cps->offeredDraw = 0;
9160     if (!appData.icsActive) {
9161         SendTimeControl(cps, movesPerSession, timeControl,
9162                         timeIncrement, appData.searchDepth,
9163                         searchTime);
9164     }
9165     if (appData.showThinking
9166         // [HGM] thinking: four options require thinking output to be sent
9167         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9168                                 ) {
9169         SendToProgram("post\n", cps);
9170     }
9171     SendToProgram("hard\n", cps);
9172     if (!appData.ponderNextMove) {
9173         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9174            it without being sure what state we are in first.  "hard"
9175            is not a toggle, so that one is OK.
9176          */
9177         SendToProgram("easy\n", cps);
9178     }
9179     if (cps->usePing) {
9180       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9181       SendToProgram(buf, cps);
9182     }
9183     cps->initDone = TRUE;
9184 }
9185
9186
9187 void
9188 StartChessProgram(cps)
9189      ChessProgramState *cps;
9190 {
9191     char buf[MSG_SIZ];
9192     int err;
9193
9194     if (appData.noChessProgram) return;
9195     cps->initDone = FALSE;
9196
9197     if (strcmp(cps->host, "localhost") == 0) {
9198         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9199     } else if (*appData.remoteShell == NULLCHAR) {
9200         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9201     } else {
9202         if (*appData.remoteUser == NULLCHAR) {
9203           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9204                     cps->program);
9205         } else {
9206           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9207                     cps->host, appData.remoteUser, cps->program);
9208         }
9209         err = StartChildProcess(buf, "", &cps->pr);
9210     }
9211
9212     if (err != 0) {
9213       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9214         DisplayFatalError(buf, err, 1);
9215         cps->pr = NoProc;
9216         cps->isr = NULL;
9217         return;
9218     }
9219
9220     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9221     if (cps->protocolVersion > 1) {
9222       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9223       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9224       cps->comboCnt = 0;  //                and values of combo boxes
9225       SendToProgram(buf, cps);
9226     } else {
9227       SendToProgram("xboard\n", cps);
9228     }
9229 }
9230
9231
9232 void
9233 TwoMachinesEventIfReady P((void))
9234 {
9235   if (first.lastPing != first.lastPong) {
9236     DisplayMessage("", _("Waiting for first chess program"));
9237     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9238     return;
9239   }
9240   if (second.lastPing != second.lastPong) {
9241     DisplayMessage("", _("Waiting for second chess program"));
9242     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9243     return;
9244   }
9245   ThawUI();
9246   TwoMachinesEvent();
9247 }
9248
9249 void
9250 NextMatchGame P((void))
9251 {
9252     int index; /* [HGM] autoinc: step load index during match */
9253     Reset(FALSE, TRUE);
9254     if (*appData.loadGameFile != NULLCHAR) {
9255         index = appData.loadGameIndex;
9256         if(index < 0) { // [HGM] autoinc
9257             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9258             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9259         }
9260         LoadGameFromFile(appData.loadGameFile,
9261                          index,
9262                          appData.loadGameFile, FALSE);
9263     } else if (*appData.loadPositionFile != NULLCHAR) {
9264         index = appData.loadPositionIndex;
9265         if(index < 0) { // [HGM] autoinc
9266             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9267             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9268         }
9269         LoadPositionFromFile(appData.loadPositionFile,
9270                              index,
9271                              appData.loadPositionFile);
9272     }
9273     TwoMachinesEventIfReady();
9274 }
9275
9276 void UserAdjudicationEvent( int result )
9277 {
9278     ChessMove gameResult = GameIsDrawn;
9279
9280     if( result > 0 ) {
9281         gameResult = WhiteWins;
9282     }
9283     else if( result < 0 ) {
9284         gameResult = BlackWins;
9285     }
9286
9287     if( gameMode == TwoMachinesPlay ) {
9288         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9289     }
9290 }
9291
9292
9293 // [HGM] save: calculate checksum of game to make games easily identifiable
9294 int StringCheckSum(char *s)
9295 {
9296         int i = 0;
9297         if(s==NULL) return 0;
9298         while(*s) i = i*259 + *s++;
9299         return i;
9300 }
9301
9302 int GameCheckSum()
9303 {
9304         int i, sum=0;
9305         for(i=backwardMostMove; i<forwardMostMove; i++) {
9306                 sum += pvInfoList[i].depth;
9307                 sum += StringCheckSum(parseList[i]);
9308                 sum += StringCheckSum(commentList[i]);
9309                 sum *= 261;
9310         }
9311         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9312         return sum + StringCheckSum(commentList[i]);
9313 } // end of save patch
9314
9315 void
9316 GameEnds(result, resultDetails, whosays)
9317      ChessMove result;
9318      char *resultDetails;
9319      int whosays;
9320 {
9321     GameMode nextGameMode;
9322     int isIcsGame;
9323     char buf[MSG_SIZ], popupRequested = 0;
9324
9325     if(endingGame) return; /* [HGM] crash: forbid recursion */
9326     endingGame = 1;
9327     if(twoBoards) { // [HGM] dual: switch back to one board
9328         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9329         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9330     }
9331     if (appData.debugMode) {
9332       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9333               result, resultDetails ? resultDetails : "(null)", whosays);
9334     }
9335
9336     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9337
9338     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9339         /* If we are playing on ICS, the server decides when the
9340            game is over, but the engine can offer to draw, claim
9341            a draw, or resign.
9342          */
9343 #if ZIPPY
9344         if (appData.zippyPlay && first.initDone) {
9345             if (result == GameIsDrawn) {
9346                 /* In case draw still needs to be claimed */
9347                 SendToICS(ics_prefix);
9348                 SendToICS("draw\n");
9349             } else if (StrCaseStr(resultDetails, "resign")) {
9350                 SendToICS(ics_prefix);
9351                 SendToICS("resign\n");
9352             }
9353         }
9354 #endif
9355         endingGame = 0; /* [HGM] crash */
9356         return;
9357     }
9358
9359     /* If we're loading the game from a file, stop */
9360     if (whosays == GE_FILE) {
9361       (void) StopLoadGameTimer();
9362       gameFileFP = NULL;
9363     }
9364
9365     /* Cancel draw offers */
9366     first.offeredDraw = second.offeredDraw = 0;
9367
9368     /* If this is an ICS game, only ICS can really say it's done;
9369        if not, anyone can. */
9370     isIcsGame = (gameMode == IcsPlayingWhite ||
9371                  gameMode == IcsPlayingBlack ||
9372                  gameMode == IcsObserving    ||
9373                  gameMode == IcsExamining);
9374
9375     if (!isIcsGame || whosays == GE_ICS) {
9376         /* OK -- not an ICS game, or ICS said it was done */
9377         StopClocks();
9378         if (!isIcsGame && !appData.noChessProgram)
9379           SetUserThinkingEnables();
9380
9381         /* [HGM] if a machine claims the game end we verify this claim */
9382         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9383             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9384                 char claimer;
9385                 ChessMove trueResult = (ChessMove) -1;
9386
9387                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9388                                             first.twoMachinesColor[0] :
9389                                             second.twoMachinesColor[0] ;
9390
9391                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9392                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9393                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9394                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9395                 } else
9396                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9397                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9398                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9399                 } else
9400                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9401                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9402                 }
9403
9404                 // now verify win claims, but not in drop games, as we don't understand those yet
9405                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9406                                                  || gameInfo.variant == VariantGreat) &&
9407                     (result == WhiteWins && claimer == 'w' ||
9408                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9409                       if (appData.debugMode) {
9410                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9411                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9412                       }
9413                       if(result != trueResult) {
9414                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9415                               result = claimer == 'w' ? BlackWins : WhiteWins;
9416                               resultDetails = buf;
9417                       }
9418                 } else
9419                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9420                     && (forwardMostMove <= backwardMostMove ||
9421                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9422                         (claimer=='b')==(forwardMostMove&1))
9423                                                                                   ) {
9424                       /* [HGM] verify: draws that were not flagged are false claims */
9425                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9426                       result = claimer == 'w' ? BlackWins : WhiteWins;
9427                       resultDetails = buf;
9428                 }
9429                 /* (Claiming a loss is accepted no questions asked!) */
9430             }
9431             /* [HGM] bare: don't allow bare King to win */
9432             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9433                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9434                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9435                && result != GameIsDrawn)
9436             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9437                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9438                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9439                         if(p >= 0 && p <= (int)WhiteKing) k++;
9440                 }
9441                 if (appData.debugMode) {
9442                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9443                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9444                 }
9445                 if(k <= 1) {
9446                         result = GameIsDrawn;
9447                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9448                         resultDetails = buf;
9449                 }
9450             }
9451         }
9452
9453
9454         if(serverMoves != NULL && !loadFlag) { char c = '=';
9455             if(result==WhiteWins) c = '+';
9456             if(result==BlackWins) c = '-';
9457             if(resultDetails != NULL)
9458                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9459         }
9460         if (resultDetails != NULL) {
9461             gameInfo.result = result;
9462             gameInfo.resultDetails = StrSave(resultDetails);
9463
9464             /* display last move only if game was not loaded from file */
9465             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9466                 DisplayMove(currentMove - 1);
9467
9468             if (forwardMostMove != 0) {
9469                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9470                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9471                                                                 ) {
9472                     if (*appData.saveGameFile != NULLCHAR) {
9473                         SaveGameToFile(appData.saveGameFile, TRUE);
9474                     } else if (appData.autoSaveGames) {
9475                         AutoSaveGame();
9476                     }
9477                     if (*appData.savePositionFile != NULLCHAR) {
9478                         SavePositionToFile(appData.savePositionFile);
9479                     }
9480                 }
9481             }
9482
9483             /* Tell program how game ended in case it is learning */
9484             /* [HGM] Moved this to after saving the PGN, just in case */
9485             /* engine died and we got here through time loss. In that */
9486             /* case we will get a fatal error writing the pipe, which */
9487             /* would otherwise lose us the PGN.                       */
9488             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9489             /* output during GameEnds should never be fatal anymore   */
9490             if (gameMode == MachinePlaysWhite ||
9491                 gameMode == MachinePlaysBlack ||
9492                 gameMode == TwoMachinesPlay ||
9493                 gameMode == IcsPlayingWhite ||
9494                 gameMode == IcsPlayingBlack ||
9495                 gameMode == BeginningOfGame) {
9496                 char buf[MSG_SIZ];
9497                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9498                         resultDetails);
9499                 if (first.pr != NoProc) {
9500                     SendToProgram(buf, &first);
9501                 }
9502                 if (second.pr != NoProc &&
9503                     gameMode == TwoMachinesPlay) {
9504                     SendToProgram(buf, &second);
9505                 }
9506             }
9507         }
9508
9509         if (appData.icsActive) {
9510             if (appData.quietPlay &&
9511                 (gameMode == IcsPlayingWhite ||
9512                  gameMode == IcsPlayingBlack)) {
9513                 SendToICS(ics_prefix);
9514                 SendToICS("set shout 1\n");
9515             }
9516             nextGameMode = IcsIdle;
9517             ics_user_moved = FALSE;
9518             /* clean up premove.  It's ugly when the game has ended and the
9519              * premove highlights are still on the board.
9520              */
9521             if (gotPremove) {
9522               gotPremove = FALSE;
9523               ClearPremoveHighlights();
9524               DrawPosition(FALSE, boards[currentMove]);
9525             }
9526             if (whosays == GE_ICS) {
9527                 switch (result) {
9528                 case WhiteWins:
9529                     if (gameMode == IcsPlayingWhite)
9530                         PlayIcsWinSound();
9531                     else if(gameMode == IcsPlayingBlack)
9532                         PlayIcsLossSound();
9533                     break;
9534                 case BlackWins:
9535                     if (gameMode == IcsPlayingBlack)
9536                         PlayIcsWinSound();
9537                     else if(gameMode == IcsPlayingWhite)
9538                         PlayIcsLossSound();
9539                     break;
9540                 case GameIsDrawn:
9541                     PlayIcsDrawSound();
9542                     break;
9543                 default:
9544                     PlayIcsUnfinishedSound();
9545                 }
9546             }
9547         } else if (gameMode == EditGame ||
9548                    gameMode == PlayFromGameFile ||
9549                    gameMode == AnalyzeMode ||
9550                    gameMode == AnalyzeFile) {
9551             nextGameMode = gameMode;
9552         } else {
9553             nextGameMode = EndOfGame;
9554         }
9555         pausing = FALSE;
9556         ModeHighlight();
9557     } else {
9558         nextGameMode = gameMode;
9559     }
9560
9561     if (appData.noChessProgram) {
9562         gameMode = nextGameMode;
9563         ModeHighlight();
9564         endingGame = 0; /* [HGM] crash */
9565         return;
9566     }
9567
9568     if (first.reuse) {
9569         /* Put first chess program into idle state */
9570         if (first.pr != NoProc &&
9571             (gameMode == MachinePlaysWhite ||
9572              gameMode == MachinePlaysBlack ||
9573              gameMode == TwoMachinesPlay ||
9574              gameMode == IcsPlayingWhite ||
9575              gameMode == IcsPlayingBlack ||
9576              gameMode == BeginningOfGame)) {
9577             SendToProgram("force\n", &first);
9578             if (first.usePing) {
9579               char buf[MSG_SIZ];
9580               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9581               SendToProgram(buf, &first);
9582             }
9583         }
9584     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9585         /* Kill off first chess program */
9586         if (first.isr != NULL)
9587           RemoveInputSource(first.isr);
9588         first.isr = NULL;
9589
9590         if (first.pr != NoProc) {
9591             ExitAnalyzeMode();
9592             DoSleep( appData.delayBeforeQuit );
9593             SendToProgram("quit\n", &first);
9594             DoSleep( appData.delayAfterQuit );
9595             DestroyChildProcess(first.pr, first.useSigterm);
9596         }
9597         first.pr = NoProc;
9598     }
9599     if (second.reuse) {
9600         /* Put second chess program into idle state */
9601         if (second.pr != NoProc &&
9602             gameMode == TwoMachinesPlay) {
9603             SendToProgram("force\n", &second);
9604             if (second.usePing) {
9605               char buf[MSG_SIZ];
9606               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9607               SendToProgram(buf, &second);
9608             }
9609         }
9610     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9611         /* Kill off second chess program */
9612         if (second.isr != NULL)
9613           RemoveInputSource(second.isr);
9614         second.isr = NULL;
9615
9616         if (second.pr != NoProc) {
9617             DoSleep( appData.delayBeforeQuit );
9618             SendToProgram("quit\n", &second);
9619             DoSleep( appData.delayAfterQuit );
9620             DestroyChildProcess(second.pr, second.useSigterm);
9621         }
9622         second.pr = NoProc;
9623     }
9624
9625     if (matchMode && gameMode == TwoMachinesPlay) {
9626         switch (result) {
9627         case WhiteWins:
9628           if (first.twoMachinesColor[0] == 'w') {
9629             first.matchWins++;
9630           } else {
9631             second.matchWins++;
9632           }
9633           break;
9634         case BlackWins:
9635           if (first.twoMachinesColor[0] == 'b') {
9636             first.matchWins++;
9637           } else {
9638             second.matchWins++;
9639           }
9640           break;
9641         default:
9642           break;
9643         }
9644         if (matchGame < appData.matchGames) {
9645             char *tmp;
9646             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9647                 tmp = first.twoMachinesColor;
9648                 first.twoMachinesColor = second.twoMachinesColor;
9649                 second.twoMachinesColor = tmp;
9650             }
9651             gameMode = nextGameMode;
9652             matchGame++;
9653             if(appData.matchPause>10000 || appData.matchPause<10)
9654                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9655             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9656             endingGame = 0; /* [HGM] crash */
9657             return;
9658         } else {
9659             gameMode = nextGameMode;
9660             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9661                      first.tidy, second.tidy,
9662                      first.matchWins, second.matchWins,
9663                      appData.matchGames - (first.matchWins + second.matchWins));
9664             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9665             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9666                 first.twoMachinesColor = "black\n";
9667                 second.twoMachinesColor = "white\n";
9668             } else {
9669                 first.twoMachinesColor = "white\n";
9670                 second.twoMachinesColor = "black\n";
9671             }
9672         }
9673     }
9674     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9675         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9676       ExitAnalyzeMode();
9677     gameMode = nextGameMode;
9678     ModeHighlight();
9679     endingGame = 0;  /* [HGM] crash */
9680     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9681       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9682         matchMode = FALSE; appData.matchGames = matchGame = 0;
9683         DisplayNote(buf);
9684       }
9685     }
9686 }
9687
9688 /* Assumes program was just initialized (initString sent).
9689    Leaves program in force mode. */
9690 void
9691 FeedMovesToProgram(cps, upto)
9692      ChessProgramState *cps;
9693      int upto;
9694 {
9695     int i;
9696
9697     if (appData.debugMode)
9698       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9699               startedFromSetupPosition ? "position and " : "",
9700               backwardMostMove, upto, cps->which);
9701     if(currentlyInitializedVariant != gameInfo.variant) {
9702       char buf[MSG_SIZ];
9703         // [HGM] variantswitch: make engine aware of new variant
9704         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9705                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9706         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9707         SendToProgram(buf, cps);
9708         currentlyInitializedVariant = gameInfo.variant;
9709     }
9710     SendToProgram("force\n", cps);
9711     if (startedFromSetupPosition) {
9712         SendBoard(cps, backwardMostMove);
9713     if (appData.debugMode) {
9714         fprintf(debugFP, "feedMoves\n");
9715     }
9716     }
9717     for (i = backwardMostMove; i < upto; i++) {
9718         SendMoveToProgram(i, cps);
9719     }
9720 }
9721
9722
9723 void
9724 ResurrectChessProgram()
9725 {
9726      /* The chess program may have exited.
9727         If so, restart it and feed it all the moves made so far. */
9728
9729     if (appData.noChessProgram || first.pr != NoProc) return;
9730
9731     StartChessProgram(&first);
9732     InitChessProgram(&first, FALSE);
9733     FeedMovesToProgram(&first, currentMove);
9734
9735     if (!first.sendTime) {
9736         /* can't tell gnuchess what its clock should read,
9737            so we bow to its notion. */
9738         ResetClocks();
9739         timeRemaining[0][currentMove] = whiteTimeRemaining;
9740         timeRemaining[1][currentMove] = blackTimeRemaining;
9741     }
9742
9743     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9744                 appData.icsEngineAnalyze) && first.analysisSupport) {
9745       SendToProgram("analyze\n", &first);
9746       first.analyzing = TRUE;
9747     }
9748 }
9749
9750 /*
9751  * Button procedures
9752  */
9753 void
9754 Reset(redraw, init)
9755      int redraw, init;
9756 {
9757     int i;
9758
9759     if (appData.debugMode) {
9760         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9761                 redraw, init, gameMode);
9762     }
9763     CleanupTail(); // [HGM] vari: delete any stored variations
9764     pausing = pauseExamInvalid = FALSE;
9765     startedFromSetupPosition = blackPlaysFirst = FALSE;
9766     firstMove = TRUE;
9767     whiteFlag = blackFlag = FALSE;
9768     userOfferedDraw = FALSE;
9769     hintRequested = bookRequested = FALSE;
9770     first.maybeThinking = FALSE;
9771     second.maybeThinking = FALSE;
9772     first.bookSuspend = FALSE; // [HGM] book
9773     second.bookSuspend = FALSE;
9774     thinkOutput[0] = NULLCHAR;
9775     lastHint[0] = NULLCHAR;
9776     ClearGameInfo(&gameInfo);
9777     gameInfo.variant = StringToVariant(appData.variant);
9778     ics_user_moved = ics_clock_paused = FALSE;
9779     ics_getting_history = H_FALSE;
9780     ics_gamenum = -1;
9781     white_holding[0] = black_holding[0] = NULLCHAR;
9782     ClearProgramStats();
9783     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9784
9785     ResetFrontEnd();
9786     ClearHighlights();
9787     flipView = appData.flipView;
9788     ClearPremoveHighlights();
9789     gotPremove = FALSE;
9790     alarmSounded = FALSE;
9791
9792     GameEnds(EndOfFile, NULL, GE_PLAYER);
9793     if(appData.serverMovesName != NULL) {
9794         /* [HGM] prepare to make moves file for broadcasting */
9795         clock_t t = clock();
9796         if(serverMoves != NULL) fclose(serverMoves);
9797         serverMoves = fopen(appData.serverMovesName, "r");
9798         if(serverMoves != NULL) {
9799             fclose(serverMoves);
9800             /* delay 15 sec before overwriting, so all clients can see end */
9801             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9802         }
9803         serverMoves = fopen(appData.serverMovesName, "w");
9804     }
9805
9806     ExitAnalyzeMode();
9807     gameMode = BeginningOfGame;
9808     ModeHighlight();
9809     if(appData.icsActive) gameInfo.variant = VariantNormal;
9810     currentMove = forwardMostMove = backwardMostMove = 0;
9811     InitPosition(redraw);
9812     for (i = 0; i < MAX_MOVES; i++) {
9813         if (commentList[i] != NULL) {
9814             free(commentList[i]);
9815             commentList[i] = NULL;
9816         }
9817     }
9818     ResetClocks();
9819     timeRemaining[0][0] = whiteTimeRemaining;
9820     timeRemaining[1][0] = blackTimeRemaining;
9821     if (first.pr == NULL) {
9822         StartChessProgram(&first);
9823     }
9824     if (init) {
9825             InitChessProgram(&first, startedFromSetupPosition);
9826     }
9827     DisplayTitle("");
9828     DisplayMessage("", "");
9829     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9830     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9831 }
9832
9833 void
9834 AutoPlayGameLoop()
9835 {
9836     for (;;) {
9837         if (!AutoPlayOneMove())
9838           return;
9839         if (matchMode || appData.timeDelay == 0)
9840           continue;
9841         if (appData.timeDelay < 0)
9842           return;
9843         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9844         break;
9845     }
9846 }
9847
9848
9849 int
9850 AutoPlayOneMove()
9851 {
9852     int fromX, fromY, toX, toY;
9853
9854     if (appData.debugMode) {
9855       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9856     }
9857
9858     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9859       return FALSE;
9860
9861     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9862       pvInfoList[currentMove].depth = programStats.depth;
9863       pvInfoList[currentMove].score = programStats.score;
9864       pvInfoList[currentMove].time  = 0;
9865       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9866     }
9867
9868     if (currentMove >= forwardMostMove) {
9869       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9870       gameMode = EditGame;
9871       ModeHighlight();
9872
9873       /* [AS] Clear current move marker at the end of a game */
9874       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9875
9876       return FALSE;
9877     }
9878
9879     toX = moveList[currentMove][2] - AAA;
9880     toY = moveList[currentMove][3] - ONE;
9881
9882     if (moveList[currentMove][1] == '@') {
9883         if (appData.highlightLastMove) {
9884             SetHighlights(-1, -1, toX, toY);
9885         }
9886     } else {
9887         fromX = moveList[currentMove][0] - AAA;
9888         fromY = moveList[currentMove][1] - ONE;
9889
9890         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9891
9892         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9893
9894         if (appData.highlightLastMove) {
9895             SetHighlights(fromX, fromY, toX, toY);
9896         }
9897     }
9898     DisplayMove(currentMove);
9899     SendMoveToProgram(currentMove++, &first);
9900     DisplayBothClocks();
9901     DrawPosition(FALSE, boards[currentMove]);
9902     // [HGM] PV info: always display, routine tests if empty
9903     DisplayComment(currentMove - 1, commentList[currentMove]);
9904     return TRUE;
9905 }
9906
9907
9908 int
9909 LoadGameOneMove(readAhead)
9910      ChessMove readAhead;
9911 {
9912     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9913     char promoChar = NULLCHAR;
9914     ChessMove moveType;
9915     char move[MSG_SIZ];
9916     char *p, *q;
9917
9918     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9919         gameMode != AnalyzeMode && gameMode != Training) {
9920         gameFileFP = NULL;
9921         return FALSE;
9922     }
9923
9924     yyboardindex = forwardMostMove;
9925     if (readAhead != EndOfFile) {
9926       moveType = readAhead;
9927     } else {
9928       if (gameFileFP == NULL)
9929           return FALSE;
9930       moveType = (ChessMove) Myylex();
9931     }
9932
9933     done = FALSE;
9934     switch (moveType) {
9935       case Comment:
9936         if (appData.debugMode)
9937           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9938         p = yy_text;
9939
9940         /* append the comment but don't display it */
9941         AppendComment(currentMove, p, FALSE);
9942         return TRUE;
9943
9944       case WhiteCapturesEnPassant:
9945       case BlackCapturesEnPassant:
9946       case WhitePromotion:
9947       case BlackPromotion:
9948       case WhiteNonPromotion:
9949       case BlackNonPromotion:
9950       case NormalMove:
9951       case WhiteKingSideCastle:
9952       case WhiteQueenSideCastle:
9953       case BlackKingSideCastle:
9954       case BlackQueenSideCastle:
9955       case WhiteKingSideCastleWild:
9956       case WhiteQueenSideCastleWild:
9957       case BlackKingSideCastleWild:
9958       case BlackQueenSideCastleWild:
9959       /* PUSH Fabien */
9960       case WhiteHSideCastleFR:
9961       case WhiteASideCastleFR:
9962       case BlackHSideCastleFR:
9963       case BlackASideCastleFR:
9964       /* POP Fabien */
9965         if (appData.debugMode)
9966           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9967         fromX = currentMoveString[0] - AAA;
9968         fromY = currentMoveString[1] - ONE;
9969         toX = currentMoveString[2] - AAA;
9970         toY = currentMoveString[3] - ONE;
9971         promoChar = currentMoveString[4];
9972         break;
9973
9974       case WhiteDrop:
9975       case BlackDrop:
9976         if (appData.debugMode)
9977           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9978         fromX = moveType == WhiteDrop ?
9979           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9980         (int) CharToPiece(ToLower(currentMoveString[0]));
9981         fromY = DROP_RANK;
9982         toX = currentMoveString[2] - AAA;
9983         toY = currentMoveString[3] - ONE;
9984         break;
9985
9986       case WhiteWins:
9987       case BlackWins:
9988       case GameIsDrawn:
9989       case GameUnfinished:
9990         if (appData.debugMode)
9991           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9992         p = strchr(yy_text, '{');
9993         if (p == NULL) p = strchr(yy_text, '(');
9994         if (p == NULL) {
9995             p = yy_text;
9996             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9997         } else {
9998             q = strchr(p, *p == '{' ? '}' : ')');
9999             if (q != NULL) *q = NULLCHAR;
10000             p++;
10001         }
10002         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10003         GameEnds(moveType, p, GE_FILE);
10004         done = TRUE;
10005         if (cmailMsgLoaded) {
10006             ClearHighlights();
10007             flipView = WhiteOnMove(currentMove);
10008             if (moveType == GameUnfinished) flipView = !flipView;
10009             if (appData.debugMode)
10010               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10011         }
10012         break;
10013
10014       case EndOfFile:
10015         if (appData.debugMode)
10016           fprintf(debugFP, "Parser hit end of file\n");
10017         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10018           case MT_NONE:
10019           case MT_CHECK:
10020             break;
10021           case MT_CHECKMATE:
10022           case MT_STAINMATE:
10023             if (WhiteOnMove(currentMove)) {
10024                 GameEnds(BlackWins, "Black mates", GE_FILE);
10025             } else {
10026                 GameEnds(WhiteWins, "White mates", GE_FILE);
10027             }
10028             break;
10029           case MT_STALEMATE:
10030             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10031             break;
10032         }
10033         done = TRUE;
10034         break;
10035
10036       case MoveNumberOne:
10037         if (lastLoadGameStart == GNUChessGame) {
10038             /* GNUChessGames have numbers, but they aren't move numbers */
10039             if (appData.debugMode)
10040               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10041                       yy_text, (int) moveType);
10042             return LoadGameOneMove(EndOfFile); /* tail recursion */
10043         }
10044         /* else fall thru */
10045
10046       case XBoardGame:
10047       case GNUChessGame:
10048       case PGNTag:
10049         /* Reached start of next game in file */
10050         if (appData.debugMode)
10051           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10052         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10053           case MT_NONE:
10054           case MT_CHECK:
10055             break;
10056           case MT_CHECKMATE:
10057           case MT_STAINMATE:
10058             if (WhiteOnMove(currentMove)) {
10059                 GameEnds(BlackWins, "Black mates", GE_FILE);
10060             } else {
10061                 GameEnds(WhiteWins, "White mates", GE_FILE);
10062             }
10063             break;
10064           case MT_STALEMATE:
10065             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10066             break;
10067         }
10068         done = TRUE;
10069         break;
10070
10071       case PositionDiagram:     /* should not happen; ignore */
10072       case ElapsedTime:         /* ignore */
10073       case NAG:                 /* ignore */
10074         if (appData.debugMode)
10075           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10076                   yy_text, (int) moveType);
10077         return LoadGameOneMove(EndOfFile); /* tail recursion */
10078
10079       case IllegalMove:
10080         if (appData.testLegality) {
10081             if (appData.debugMode)
10082               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10083             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10084                     (forwardMostMove / 2) + 1,
10085                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10086             DisplayError(move, 0);
10087             done = TRUE;
10088         } else {
10089             if (appData.debugMode)
10090               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10091                       yy_text, currentMoveString);
10092             fromX = currentMoveString[0] - AAA;
10093             fromY = currentMoveString[1] - ONE;
10094             toX = currentMoveString[2] - AAA;
10095             toY = currentMoveString[3] - ONE;
10096             promoChar = currentMoveString[4];
10097         }
10098         break;
10099
10100       case AmbiguousMove:
10101         if (appData.debugMode)
10102           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10103         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10104                 (forwardMostMove / 2) + 1,
10105                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10106         DisplayError(move, 0);
10107         done = TRUE;
10108         break;
10109
10110       default:
10111       case ImpossibleMove:
10112         if (appData.debugMode)
10113           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10114         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10115                 (forwardMostMove / 2) + 1,
10116                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10117         DisplayError(move, 0);
10118         done = TRUE;
10119         break;
10120     }
10121
10122     if (done) {
10123         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10124             DrawPosition(FALSE, boards[currentMove]);
10125             DisplayBothClocks();
10126             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10127               DisplayComment(currentMove - 1, commentList[currentMove]);
10128         }
10129         (void) StopLoadGameTimer();
10130         gameFileFP = NULL;
10131         cmailOldMove = forwardMostMove;
10132         return FALSE;
10133     } else {
10134         /* currentMoveString is set as a side-effect of yylex */
10135
10136         thinkOutput[0] = NULLCHAR;
10137         MakeMove(fromX, fromY, toX, toY, promoChar);
10138         currentMove = forwardMostMove;
10139         return TRUE;
10140     }
10141 }
10142
10143 /* Load the nth game from the given file */
10144 int
10145 LoadGameFromFile(filename, n, title, useList)
10146      char *filename;
10147      int n;
10148      char *title;
10149      /*Boolean*/ int useList;
10150 {
10151     FILE *f;
10152     char buf[MSG_SIZ];
10153
10154     if (strcmp(filename, "-") == 0) {
10155         f = stdin;
10156         title = "stdin";
10157     } else {
10158         f = fopen(filename, "rb");
10159         if (f == NULL) {
10160           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10161             DisplayError(buf, errno);
10162             return FALSE;
10163         }
10164     }
10165     if (fseek(f, 0, 0) == -1) {
10166         /* f is not seekable; probably a pipe */
10167         useList = FALSE;
10168     }
10169     if (useList && n == 0) {
10170         int error = GameListBuild(f);
10171         if (error) {
10172             DisplayError(_("Cannot build game list"), error);
10173         } else if (!ListEmpty(&gameList) &&
10174                    ((ListGame *) gameList.tailPred)->number > 1) {
10175             GameListPopUp(f, title);
10176             return TRUE;
10177         }
10178         GameListDestroy();
10179         n = 1;
10180     }
10181     if (n == 0) n = 1;
10182     return LoadGame(f, n, title, FALSE);
10183 }
10184
10185
10186 void
10187 MakeRegisteredMove()
10188 {
10189     int fromX, fromY, toX, toY;
10190     char promoChar;
10191     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10192         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10193           case CMAIL_MOVE:
10194           case CMAIL_DRAW:
10195             if (appData.debugMode)
10196               fprintf(debugFP, "Restoring %s for game %d\n",
10197                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10198
10199             thinkOutput[0] = NULLCHAR;
10200             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10201             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10202             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10203             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10204             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10205             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10206             MakeMove(fromX, fromY, toX, toY, promoChar);
10207             ShowMove(fromX, fromY, toX, toY);
10208
10209             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10210               case MT_NONE:
10211               case MT_CHECK:
10212                 break;
10213
10214               case MT_CHECKMATE:
10215               case MT_STAINMATE:
10216                 if (WhiteOnMove(currentMove)) {
10217                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10218                 } else {
10219                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10220                 }
10221                 break;
10222
10223               case MT_STALEMATE:
10224                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10225                 break;
10226             }
10227
10228             break;
10229
10230           case CMAIL_RESIGN:
10231             if (WhiteOnMove(currentMove)) {
10232                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10233             } else {
10234                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10235             }
10236             break;
10237
10238           case CMAIL_ACCEPT:
10239             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10240             break;
10241
10242           default:
10243             break;
10244         }
10245     }
10246
10247     return;
10248 }
10249
10250 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10251 int
10252 CmailLoadGame(f, gameNumber, title, useList)
10253      FILE *f;
10254      int gameNumber;
10255      char *title;
10256      int useList;
10257 {
10258     int retVal;
10259
10260     if (gameNumber > nCmailGames) {
10261         DisplayError(_("No more games in this message"), 0);
10262         return FALSE;
10263     }
10264     if (f == lastLoadGameFP) {
10265         int offset = gameNumber - lastLoadGameNumber;
10266         if (offset == 0) {
10267             cmailMsg[0] = NULLCHAR;
10268             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10269                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10270                 nCmailMovesRegistered--;
10271             }
10272             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10273             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10274                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10275             }
10276         } else {
10277             if (! RegisterMove()) return FALSE;
10278         }
10279     }
10280
10281     retVal = LoadGame(f, gameNumber, title, useList);
10282
10283     /* Make move registered during previous look at this game, if any */
10284     MakeRegisteredMove();
10285
10286     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10287         commentList[currentMove]
10288           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10289         DisplayComment(currentMove - 1, commentList[currentMove]);
10290     }
10291
10292     return retVal;
10293 }
10294
10295 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10296 int
10297 ReloadGame(offset)
10298      int offset;
10299 {
10300     int gameNumber = lastLoadGameNumber + offset;
10301     if (lastLoadGameFP == NULL) {
10302         DisplayError(_("No game has been loaded yet"), 0);
10303         return FALSE;
10304     }
10305     if (gameNumber <= 0) {
10306         DisplayError(_("Can't back up any further"), 0);
10307         return FALSE;
10308     }
10309     if (cmailMsgLoaded) {
10310         return CmailLoadGame(lastLoadGameFP, gameNumber,
10311                              lastLoadGameTitle, lastLoadGameUseList);
10312     } else {
10313         return LoadGame(lastLoadGameFP, gameNumber,
10314                         lastLoadGameTitle, lastLoadGameUseList);
10315     }
10316 }
10317
10318
10319
10320 /* Load the nth game from open file f */
10321 int
10322 LoadGame(f, gameNumber, title, useList)
10323      FILE *f;
10324      int gameNumber;
10325      char *title;
10326      int useList;
10327 {
10328     ChessMove cm;
10329     char buf[MSG_SIZ];
10330     int gn = gameNumber;
10331     ListGame *lg = NULL;
10332     int numPGNTags = 0;
10333     int err;
10334     GameMode oldGameMode;
10335     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10336
10337     if (appData.debugMode)
10338         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10339
10340     if (gameMode == Training )
10341         SetTrainingModeOff();
10342
10343     oldGameMode = gameMode;
10344     if (gameMode != BeginningOfGame) {
10345       Reset(FALSE, TRUE);
10346     }
10347
10348     gameFileFP = f;
10349     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10350         fclose(lastLoadGameFP);
10351     }
10352
10353     if (useList) {
10354         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10355
10356         if (lg) {
10357             fseek(f, lg->offset, 0);
10358             GameListHighlight(gameNumber);
10359             gn = 1;
10360         }
10361         else {
10362             DisplayError(_("Game number out of range"), 0);
10363             return FALSE;
10364         }
10365     } else {
10366         GameListDestroy();
10367         if (fseek(f, 0, 0) == -1) {
10368             if (f == lastLoadGameFP ?
10369                 gameNumber == lastLoadGameNumber + 1 :
10370                 gameNumber == 1) {
10371                 gn = 1;
10372             } else {
10373                 DisplayError(_("Can't seek on game file"), 0);
10374                 return FALSE;
10375             }
10376         }
10377     }
10378     lastLoadGameFP = f;
10379     lastLoadGameNumber = gameNumber;
10380     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10381     lastLoadGameUseList = useList;
10382
10383     yynewfile(f);
10384
10385     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10386       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10387                 lg->gameInfo.black);
10388             DisplayTitle(buf);
10389     } else if (*title != NULLCHAR) {
10390         if (gameNumber > 1) {
10391           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10392             DisplayTitle(buf);
10393         } else {
10394             DisplayTitle(title);
10395         }
10396     }
10397
10398     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10399         gameMode = PlayFromGameFile;
10400         ModeHighlight();
10401     }
10402
10403     currentMove = forwardMostMove = backwardMostMove = 0;
10404     CopyBoard(boards[0], initialPosition);
10405     StopClocks();
10406
10407     /*
10408      * Skip the first gn-1 games in the file.
10409      * Also skip over anything that precedes an identifiable
10410      * start of game marker, to avoid being confused by
10411      * garbage at the start of the file.  Currently
10412      * recognized start of game markers are the move number "1",
10413      * the pattern "gnuchess .* game", the pattern
10414      * "^[#;%] [^ ]* game file", and a PGN tag block.
10415      * A game that starts with one of the latter two patterns
10416      * will also have a move number 1, possibly
10417      * following a position diagram.
10418      * 5-4-02: Let's try being more lenient and allowing a game to
10419      * start with an unnumbered move.  Does that break anything?
10420      */
10421     cm = lastLoadGameStart = EndOfFile;
10422     while (gn > 0) {
10423         yyboardindex = forwardMostMove;
10424         cm = (ChessMove) Myylex();
10425         switch (cm) {
10426           case EndOfFile:
10427             if (cmailMsgLoaded) {
10428                 nCmailGames = CMAIL_MAX_GAMES - gn;
10429             } else {
10430                 Reset(TRUE, TRUE);
10431                 DisplayError(_("Game not found in file"), 0);
10432             }
10433             return FALSE;
10434
10435           case GNUChessGame:
10436           case XBoardGame:
10437             gn--;
10438             lastLoadGameStart = cm;
10439             break;
10440
10441           case MoveNumberOne:
10442             switch (lastLoadGameStart) {
10443               case GNUChessGame:
10444               case XBoardGame:
10445               case PGNTag:
10446                 break;
10447               case MoveNumberOne:
10448               case EndOfFile:
10449                 gn--;           /* count this game */
10450                 lastLoadGameStart = cm;
10451                 break;
10452               default:
10453                 /* impossible */
10454                 break;
10455             }
10456             break;
10457
10458           case PGNTag:
10459             switch (lastLoadGameStart) {
10460               case GNUChessGame:
10461               case PGNTag:
10462               case MoveNumberOne:
10463               case EndOfFile:
10464                 gn--;           /* count this game */
10465                 lastLoadGameStart = cm;
10466                 break;
10467               case XBoardGame:
10468                 lastLoadGameStart = cm; /* game counted already */
10469                 break;
10470               default:
10471                 /* impossible */
10472                 break;
10473             }
10474             if (gn > 0) {
10475                 do {
10476                     yyboardindex = forwardMostMove;
10477                     cm = (ChessMove) Myylex();
10478                 } while (cm == PGNTag || cm == Comment);
10479             }
10480             break;
10481
10482           case WhiteWins:
10483           case BlackWins:
10484           case GameIsDrawn:
10485             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10486                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10487                     != CMAIL_OLD_RESULT) {
10488                     nCmailResults ++ ;
10489                     cmailResult[  CMAIL_MAX_GAMES
10490                                 - gn - 1] = CMAIL_OLD_RESULT;
10491                 }
10492             }
10493             break;
10494
10495           case NormalMove:
10496             /* Only a NormalMove can be at the start of a game
10497              * without a position diagram. */
10498             if (lastLoadGameStart == EndOfFile ) {
10499               gn--;
10500               lastLoadGameStart = MoveNumberOne;
10501             }
10502             break;
10503
10504           default:
10505             break;
10506         }
10507     }
10508
10509     if (appData.debugMode)
10510       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10511
10512     if (cm == XBoardGame) {
10513         /* Skip any header junk before position diagram and/or move 1 */
10514         for (;;) {
10515             yyboardindex = forwardMostMove;
10516             cm = (ChessMove) Myylex();
10517
10518             if (cm == EndOfFile ||
10519                 cm == GNUChessGame || cm == XBoardGame) {
10520                 /* Empty game; pretend end-of-file and handle later */
10521                 cm = EndOfFile;
10522                 break;
10523             }
10524
10525             if (cm == MoveNumberOne || cm == PositionDiagram ||
10526                 cm == PGNTag || cm == Comment)
10527               break;
10528         }
10529     } else if (cm == GNUChessGame) {
10530         if (gameInfo.event != NULL) {
10531             free(gameInfo.event);
10532         }
10533         gameInfo.event = StrSave(yy_text);
10534     }
10535
10536     startedFromSetupPosition = FALSE;
10537     while (cm == PGNTag) {
10538         if (appData.debugMode)
10539           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10540         err = ParsePGNTag(yy_text, &gameInfo);
10541         if (!err) numPGNTags++;
10542
10543         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10544         if(gameInfo.variant != oldVariant) {
10545             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10546             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10547             InitPosition(TRUE);
10548             oldVariant = gameInfo.variant;
10549             if (appData.debugMode)
10550               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10551         }
10552
10553
10554         if (gameInfo.fen != NULL) {
10555           Board initial_position;
10556           startedFromSetupPosition = TRUE;
10557           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10558             Reset(TRUE, TRUE);
10559             DisplayError(_("Bad FEN position in file"), 0);
10560             return FALSE;
10561           }
10562           CopyBoard(boards[0], initial_position);
10563           if (blackPlaysFirst) {
10564             currentMove = forwardMostMove = backwardMostMove = 1;
10565             CopyBoard(boards[1], initial_position);
10566             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10567             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10568             timeRemaining[0][1] = whiteTimeRemaining;
10569             timeRemaining[1][1] = blackTimeRemaining;
10570             if (commentList[0] != NULL) {
10571               commentList[1] = commentList[0];
10572               commentList[0] = NULL;
10573             }
10574           } else {
10575             currentMove = forwardMostMove = backwardMostMove = 0;
10576           }
10577           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10578           {   int i;
10579               initialRulePlies = FENrulePlies;
10580               for( i=0; i< nrCastlingRights; i++ )
10581                   initialRights[i] = initial_position[CASTLING][i];
10582           }
10583           yyboardindex = forwardMostMove;
10584           free(gameInfo.fen);
10585           gameInfo.fen = NULL;
10586         }
10587
10588         yyboardindex = forwardMostMove;
10589         cm = (ChessMove) Myylex();
10590
10591         /* Handle comments interspersed among the tags */
10592         while (cm == Comment) {
10593             char *p;
10594             if (appData.debugMode)
10595               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10596             p = yy_text;
10597             AppendComment(currentMove, p, FALSE);
10598             yyboardindex = forwardMostMove;
10599             cm = (ChessMove) Myylex();
10600         }
10601     }
10602
10603     /* don't rely on existence of Event tag since if game was
10604      * pasted from clipboard the Event tag may not exist
10605      */
10606     if (numPGNTags > 0){
10607         char *tags;
10608         if (gameInfo.variant == VariantNormal) {
10609           VariantClass v = StringToVariant(gameInfo.event);
10610           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10611           if(v < VariantShogi) gameInfo.variant = v;
10612         }
10613         if (!matchMode) {
10614           if( appData.autoDisplayTags ) {
10615             tags = PGNTags(&gameInfo);
10616             TagsPopUp(tags, CmailMsg());
10617             free(tags);
10618           }
10619         }
10620     } else {
10621         /* Make something up, but don't display it now */
10622         SetGameInfo();
10623         TagsPopDown();
10624     }
10625
10626     if (cm == PositionDiagram) {
10627         int i, j;
10628         char *p;
10629         Board initial_position;
10630
10631         if (appData.debugMode)
10632           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10633
10634         if (!startedFromSetupPosition) {
10635             p = yy_text;
10636             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10637               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10638                 switch (*p) {
10639                   case '{':
10640                   case '[':
10641                   case '-':
10642                   case ' ':
10643                   case '\t':
10644                   case '\n':
10645                   case '\r':
10646                     break;
10647                   default:
10648                     initial_position[i][j++] = CharToPiece(*p);
10649                     break;
10650                 }
10651             while (*p == ' ' || *p == '\t' ||
10652                    *p == '\n' || *p == '\r') p++;
10653
10654             if (strncmp(p, "black", strlen("black"))==0)
10655               blackPlaysFirst = TRUE;
10656             else
10657               blackPlaysFirst = FALSE;
10658             startedFromSetupPosition = TRUE;
10659
10660             CopyBoard(boards[0], initial_position);
10661             if (blackPlaysFirst) {
10662                 currentMove = forwardMostMove = backwardMostMove = 1;
10663                 CopyBoard(boards[1], initial_position);
10664                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10665                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10666                 timeRemaining[0][1] = whiteTimeRemaining;
10667                 timeRemaining[1][1] = blackTimeRemaining;
10668                 if (commentList[0] != NULL) {
10669                     commentList[1] = commentList[0];
10670                     commentList[0] = NULL;
10671                 }
10672             } else {
10673                 currentMove = forwardMostMove = backwardMostMove = 0;
10674             }
10675         }
10676         yyboardindex = forwardMostMove;
10677         cm = (ChessMove) Myylex();
10678     }
10679
10680     if (first.pr == NoProc) {
10681         StartChessProgram(&first);
10682     }
10683     InitChessProgram(&first, FALSE);
10684     SendToProgram("force\n", &first);
10685     if (startedFromSetupPosition) {
10686         SendBoard(&first, forwardMostMove);
10687     if (appData.debugMode) {
10688         fprintf(debugFP, "Load Game\n");
10689     }
10690         DisplayBothClocks();
10691     }
10692
10693     /* [HGM] server: flag to write setup moves in broadcast file as one */
10694     loadFlag = appData.suppressLoadMoves;
10695
10696     while (cm == Comment) {
10697         char *p;
10698         if (appData.debugMode)
10699           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10700         p = yy_text;
10701         AppendComment(currentMove, p, FALSE);
10702         yyboardindex = forwardMostMove;
10703         cm = (ChessMove) Myylex();
10704     }
10705
10706     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10707         cm == WhiteWins || cm == BlackWins ||
10708         cm == GameIsDrawn || cm == GameUnfinished) {
10709         DisplayMessage("", _("No moves in game"));
10710         if (cmailMsgLoaded) {
10711             if (appData.debugMode)
10712               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10713             ClearHighlights();
10714             flipView = FALSE;
10715         }
10716         DrawPosition(FALSE, boards[currentMove]);
10717         DisplayBothClocks();
10718         gameMode = EditGame;
10719         ModeHighlight();
10720         gameFileFP = NULL;
10721         cmailOldMove = 0;
10722         return TRUE;
10723     }
10724
10725     // [HGM] PV info: routine tests if comment empty
10726     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10727         DisplayComment(currentMove - 1, commentList[currentMove]);
10728     }
10729     if (!matchMode && appData.timeDelay != 0)
10730       DrawPosition(FALSE, boards[currentMove]);
10731
10732     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10733       programStats.ok_to_send = 1;
10734     }
10735
10736     /* if the first token after the PGN tags is a move
10737      * and not move number 1, retrieve it from the parser
10738      */
10739     if (cm != MoveNumberOne)
10740         LoadGameOneMove(cm);
10741
10742     /* load the remaining moves from the file */
10743     while (LoadGameOneMove(EndOfFile)) {
10744       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10745       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10746     }
10747
10748     /* rewind to the start of the game */
10749     currentMove = backwardMostMove;
10750
10751     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10752
10753     if (oldGameMode == AnalyzeFile ||
10754         oldGameMode == AnalyzeMode) {
10755       AnalyzeFileEvent();
10756     }
10757
10758     if (matchMode || appData.timeDelay == 0) {
10759       ToEndEvent();
10760       gameMode = EditGame;
10761       ModeHighlight();
10762     } else if (appData.timeDelay > 0) {
10763       AutoPlayGameLoop();
10764     }
10765
10766     if (appData.debugMode)
10767         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10768
10769     loadFlag = 0; /* [HGM] true game starts */
10770     return TRUE;
10771 }
10772
10773 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10774 int
10775 ReloadPosition(offset)
10776      int offset;
10777 {
10778     int positionNumber = lastLoadPositionNumber + offset;
10779     if (lastLoadPositionFP == NULL) {
10780         DisplayError(_("No position has been loaded yet"), 0);
10781         return FALSE;
10782     }
10783     if (positionNumber <= 0) {
10784         DisplayError(_("Can't back up any further"), 0);
10785         return FALSE;
10786     }
10787     return LoadPosition(lastLoadPositionFP, positionNumber,
10788                         lastLoadPositionTitle);
10789 }
10790
10791 /* Load the nth position from the given file */
10792 int
10793 LoadPositionFromFile(filename, n, title)
10794      char *filename;
10795      int n;
10796      char *title;
10797 {
10798     FILE *f;
10799     char buf[MSG_SIZ];
10800
10801     if (strcmp(filename, "-") == 0) {
10802         return LoadPosition(stdin, n, "stdin");
10803     } else {
10804         f = fopen(filename, "rb");
10805         if (f == NULL) {
10806             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10807             DisplayError(buf, errno);
10808             return FALSE;
10809         } else {
10810             return LoadPosition(f, n, title);
10811         }
10812     }
10813 }
10814
10815 /* Load the nth position from the given open file, and close it */
10816 int
10817 LoadPosition(f, positionNumber, title)
10818      FILE *f;
10819      int positionNumber;
10820      char *title;
10821 {
10822     char *p, line[MSG_SIZ];
10823     Board initial_position;
10824     int i, j, fenMode, pn;
10825
10826     if (gameMode == Training )
10827         SetTrainingModeOff();
10828
10829     if (gameMode != BeginningOfGame) {
10830         Reset(FALSE, TRUE);
10831     }
10832     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10833         fclose(lastLoadPositionFP);
10834     }
10835     if (positionNumber == 0) positionNumber = 1;
10836     lastLoadPositionFP = f;
10837     lastLoadPositionNumber = positionNumber;
10838     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10839     if (first.pr == NoProc) {
10840       StartChessProgram(&first);
10841       InitChessProgram(&first, FALSE);
10842     }
10843     pn = positionNumber;
10844     if (positionNumber < 0) {
10845         /* Negative position number means to seek to that byte offset */
10846         if (fseek(f, -positionNumber, 0) == -1) {
10847             DisplayError(_("Can't seek on position file"), 0);
10848             return FALSE;
10849         };
10850         pn = 1;
10851     } else {
10852         if (fseek(f, 0, 0) == -1) {
10853             if (f == lastLoadPositionFP ?
10854                 positionNumber == lastLoadPositionNumber + 1 :
10855                 positionNumber == 1) {
10856                 pn = 1;
10857             } else {
10858                 DisplayError(_("Can't seek on position file"), 0);
10859                 return FALSE;
10860             }
10861         }
10862     }
10863     /* See if this file is FEN or old-style xboard */
10864     if (fgets(line, MSG_SIZ, f) == NULL) {
10865         DisplayError(_("Position not found in file"), 0);
10866         return FALSE;
10867     }
10868     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10869     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10870
10871     if (pn >= 2) {
10872         if (fenMode || line[0] == '#') pn--;
10873         while (pn > 0) {
10874             /* skip positions before number pn */
10875             if (fgets(line, MSG_SIZ, f) == NULL) {
10876                 Reset(TRUE, TRUE);
10877                 DisplayError(_("Position not found in file"), 0);
10878                 return FALSE;
10879             }
10880             if (fenMode || line[0] == '#') pn--;
10881         }
10882     }
10883
10884     if (fenMode) {
10885         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10886             DisplayError(_("Bad FEN position in file"), 0);
10887             return FALSE;
10888         }
10889     } else {
10890         (void) fgets(line, MSG_SIZ, f);
10891         (void) fgets(line, MSG_SIZ, f);
10892
10893         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10894             (void) fgets(line, MSG_SIZ, f);
10895             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10896                 if (*p == ' ')
10897                   continue;
10898                 initial_position[i][j++] = CharToPiece(*p);
10899             }
10900         }
10901
10902         blackPlaysFirst = FALSE;
10903         if (!feof(f)) {
10904             (void) fgets(line, MSG_SIZ, f);
10905             if (strncmp(line, "black", strlen("black"))==0)
10906               blackPlaysFirst = TRUE;
10907         }
10908     }
10909     startedFromSetupPosition = TRUE;
10910
10911     SendToProgram("force\n", &first);
10912     CopyBoard(boards[0], initial_position);
10913     if (blackPlaysFirst) {
10914         currentMove = forwardMostMove = backwardMostMove = 1;
10915         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10916         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10917         CopyBoard(boards[1], initial_position);
10918         DisplayMessage("", _("Black to play"));
10919     } else {
10920         currentMove = forwardMostMove = backwardMostMove = 0;
10921         DisplayMessage("", _("White to play"));
10922     }
10923     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10924     SendBoard(&first, forwardMostMove);
10925     if (appData.debugMode) {
10926 int i, j;
10927   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10928   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10929         fprintf(debugFP, "Load Position\n");
10930     }
10931
10932     if (positionNumber > 1) {
10933       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10934         DisplayTitle(line);
10935     } else {
10936         DisplayTitle(title);
10937     }
10938     gameMode = EditGame;
10939     ModeHighlight();
10940     ResetClocks();
10941     timeRemaining[0][1] = whiteTimeRemaining;
10942     timeRemaining[1][1] = blackTimeRemaining;
10943     DrawPosition(FALSE, boards[currentMove]);
10944
10945     return TRUE;
10946 }
10947
10948
10949 void
10950 CopyPlayerNameIntoFileName(dest, src)
10951      char **dest, *src;
10952 {
10953     while (*src != NULLCHAR && *src != ',') {
10954         if (*src == ' ') {
10955             *(*dest)++ = '_';
10956             src++;
10957         } else {
10958             *(*dest)++ = *src++;
10959         }
10960     }
10961 }
10962
10963 char *DefaultFileName(ext)
10964      char *ext;
10965 {
10966     static char def[MSG_SIZ];
10967     char *p;
10968
10969     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10970         p = def;
10971         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10972         *p++ = '-';
10973         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10974         *p++ = '.';
10975         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10976     } else {
10977         def[0] = NULLCHAR;
10978     }
10979     return def;
10980 }
10981
10982 /* Save the current game to the given file */
10983 int
10984 SaveGameToFile(filename, append)
10985      char *filename;
10986      int append;
10987 {
10988     FILE *f;
10989     char buf[MSG_SIZ];
10990
10991     if (strcmp(filename, "-") == 0) {
10992         return SaveGame(stdout, 0, NULL);
10993     } else {
10994         f = fopen(filename, append ? "a" : "w");
10995         if (f == NULL) {
10996             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10997             DisplayError(buf, errno);
10998             return FALSE;
10999         } else {
11000             return SaveGame(f, 0, NULL);
11001         }
11002     }
11003 }
11004
11005 char *
11006 SavePart(str)
11007      char *str;
11008 {
11009     static char buf[MSG_SIZ];
11010     char *p;
11011
11012     p = strchr(str, ' ');
11013     if (p == NULL) return str;
11014     strncpy(buf, str, p - str);
11015     buf[p - str] = NULLCHAR;
11016     return buf;
11017 }
11018
11019 #define PGN_MAX_LINE 75
11020
11021 #define PGN_SIDE_WHITE  0
11022 #define PGN_SIDE_BLACK  1
11023
11024 /* [AS] */
11025 static int FindFirstMoveOutOfBook( int side )
11026 {
11027     int result = -1;
11028
11029     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11030         int index = backwardMostMove;
11031         int has_book_hit = 0;
11032
11033         if( (index % 2) != side ) {
11034             index++;
11035         }
11036
11037         while( index < forwardMostMove ) {
11038             /* Check to see if engine is in book */
11039             int depth = pvInfoList[index].depth;
11040             int score = pvInfoList[index].score;
11041             int in_book = 0;
11042
11043             if( depth <= 2 ) {
11044                 in_book = 1;
11045             }
11046             else if( score == 0 && depth == 63 ) {
11047                 in_book = 1; /* Zappa */
11048             }
11049             else if( score == 2 && depth == 99 ) {
11050                 in_book = 1; /* Abrok */
11051             }
11052
11053             has_book_hit += in_book;
11054
11055             if( ! in_book ) {
11056                 result = index;
11057
11058                 break;
11059             }
11060
11061             index += 2;
11062         }
11063     }
11064
11065     return result;
11066 }
11067
11068 /* [AS] */
11069 void GetOutOfBookInfo( char * buf )
11070 {
11071     int oob[2];
11072     int i;
11073     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11074
11075     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11076     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11077
11078     *buf = '\0';
11079
11080     if( oob[0] >= 0 || oob[1] >= 0 ) {
11081         for( i=0; i<2; i++ ) {
11082             int idx = oob[i];
11083
11084             if( idx >= 0 ) {
11085                 if( i > 0 && oob[0] >= 0 ) {
11086                     strcat( buf, "   " );
11087                 }
11088
11089                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11090                 sprintf( buf+strlen(buf), "%s%.2f",
11091                     pvInfoList[idx].score >= 0 ? "+" : "",
11092                     pvInfoList[idx].score / 100.0 );
11093             }
11094         }
11095     }
11096 }
11097
11098 /* Save game in PGN style and close the file */
11099 int
11100 SaveGamePGN(f)
11101      FILE *f;
11102 {
11103     int i, offset, linelen, newblock;
11104     time_t tm;
11105 //    char *movetext;
11106     char numtext[32];
11107     int movelen, numlen, blank;
11108     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11109
11110     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11111
11112     tm = time((time_t *) NULL);
11113
11114     PrintPGNTags(f, &gameInfo);
11115
11116     if (backwardMostMove > 0 || startedFromSetupPosition) {
11117         char *fen = PositionToFEN(backwardMostMove, NULL);
11118         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11119         fprintf(f, "\n{--------------\n");
11120         PrintPosition(f, backwardMostMove);
11121         fprintf(f, "--------------}\n");
11122         free(fen);
11123     }
11124     else {
11125         /* [AS] Out of book annotation */
11126         if( appData.saveOutOfBookInfo ) {
11127             char buf[64];
11128
11129             GetOutOfBookInfo( buf );
11130
11131             if( buf[0] != '\0' ) {
11132                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11133             }
11134         }
11135
11136         fprintf(f, "\n");
11137     }
11138
11139     i = backwardMostMove;
11140     linelen = 0;
11141     newblock = TRUE;
11142
11143     while (i < forwardMostMove) {
11144         /* Print comments preceding this move */
11145         if (commentList[i] != NULL) {
11146             if (linelen > 0) fprintf(f, "\n");
11147             fprintf(f, "%s", commentList[i]);
11148             linelen = 0;
11149             newblock = TRUE;
11150         }
11151
11152         /* Format move number */
11153         if ((i % 2) == 0)
11154           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11155         else
11156           if (newblock)
11157             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11158           else
11159             numtext[0] = NULLCHAR;
11160
11161         numlen = strlen(numtext);
11162         newblock = FALSE;
11163
11164         /* Print move number */
11165         blank = linelen > 0 && numlen > 0;
11166         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11167             fprintf(f, "\n");
11168             linelen = 0;
11169             blank = 0;
11170         }
11171         if (blank) {
11172             fprintf(f, " ");
11173             linelen++;
11174         }
11175         fprintf(f, "%s", numtext);
11176         linelen += numlen;
11177
11178         /* Get move */
11179         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11180         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11181
11182         /* Print move */
11183         blank = linelen > 0 && movelen > 0;
11184         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11185             fprintf(f, "\n");
11186             linelen = 0;
11187             blank = 0;
11188         }
11189         if (blank) {
11190             fprintf(f, " ");
11191             linelen++;
11192         }
11193         fprintf(f, "%s", move_buffer);
11194         linelen += movelen;
11195
11196         /* [AS] Add PV info if present */
11197         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11198             /* [HGM] add time */
11199             char buf[MSG_SIZ]; int seconds;
11200
11201             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11202
11203             if( seconds <= 0)
11204               buf[0] = 0;
11205             else
11206               if( seconds < 30 )
11207                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11208               else
11209                 {
11210                   seconds = (seconds + 4)/10; // round to full seconds
11211                   if( seconds < 60 )
11212                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11213                   else
11214                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11215                 }
11216
11217             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11218                       pvInfoList[i].score >= 0 ? "+" : "",
11219                       pvInfoList[i].score / 100.0,
11220                       pvInfoList[i].depth,
11221                       buf );
11222
11223             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11224
11225             /* Print score/depth */
11226             blank = linelen > 0 && movelen > 0;
11227             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11228                 fprintf(f, "\n");
11229                 linelen = 0;
11230                 blank = 0;
11231             }
11232             if (blank) {
11233                 fprintf(f, " ");
11234                 linelen++;
11235             }
11236             fprintf(f, "%s", move_buffer);
11237             linelen += movelen;
11238         }
11239
11240         i++;
11241     }
11242
11243     /* Start a new line */
11244     if (linelen > 0) fprintf(f, "\n");
11245
11246     /* Print comments after last move */
11247     if (commentList[i] != NULL) {
11248         fprintf(f, "%s\n", commentList[i]);
11249     }
11250
11251     /* Print result */
11252     if (gameInfo.resultDetails != NULL &&
11253         gameInfo.resultDetails[0] != NULLCHAR) {
11254         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11255                 PGNResult(gameInfo.result));
11256     } else {
11257         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11258     }
11259
11260     fclose(f);
11261     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11262     return TRUE;
11263 }
11264
11265 /* Save game in old style and close the file */
11266 int
11267 SaveGameOldStyle(f)
11268      FILE *f;
11269 {
11270     int i, offset;
11271     time_t tm;
11272
11273     tm = time((time_t *) NULL);
11274
11275     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11276     PrintOpponents(f);
11277
11278     if (backwardMostMove > 0 || startedFromSetupPosition) {
11279         fprintf(f, "\n[--------------\n");
11280         PrintPosition(f, backwardMostMove);
11281         fprintf(f, "--------------]\n");
11282     } else {
11283         fprintf(f, "\n");
11284     }
11285
11286     i = backwardMostMove;
11287     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11288
11289     while (i < forwardMostMove) {
11290         if (commentList[i] != NULL) {
11291             fprintf(f, "[%s]\n", commentList[i]);
11292         }
11293
11294         if ((i % 2) == 1) {
11295             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11296             i++;
11297         } else {
11298             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11299             i++;
11300             if (commentList[i] != NULL) {
11301                 fprintf(f, "\n");
11302                 continue;
11303             }
11304             if (i >= forwardMostMove) {
11305                 fprintf(f, "\n");
11306                 break;
11307             }
11308             fprintf(f, "%s\n", parseList[i]);
11309             i++;
11310         }
11311     }
11312
11313     if (commentList[i] != NULL) {
11314         fprintf(f, "[%s]\n", commentList[i]);
11315     }
11316
11317     /* This isn't really the old style, but it's close enough */
11318     if (gameInfo.resultDetails != NULL &&
11319         gameInfo.resultDetails[0] != NULLCHAR) {
11320         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11321                 gameInfo.resultDetails);
11322     } else {
11323         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11324     }
11325
11326     fclose(f);
11327     return TRUE;
11328 }
11329
11330 /* Save the current game to open file f and close the file */
11331 int
11332 SaveGame(f, dummy, dummy2)
11333      FILE *f;
11334      int dummy;
11335      char *dummy2;
11336 {
11337     if (gameMode == EditPosition) EditPositionDone(TRUE);
11338     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11339     if (appData.oldSaveStyle)
11340       return SaveGameOldStyle(f);
11341     else
11342       return SaveGamePGN(f);
11343 }
11344
11345 /* Save the current position to the given file */
11346 int
11347 SavePositionToFile(filename)
11348      char *filename;
11349 {
11350     FILE *f;
11351     char buf[MSG_SIZ];
11352
11353     if (strcmp(filename, "-") == 0) {
11354         return SavePosition(stdout, 0, NULL);
11355     } else {
11356         f = fopen(filename, "a");
11357         if (f == NULL) {
11358             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11359             DisplayError(buf, errno);
11360             return FALSE;
11361         } else {
11362             SavePosition(f, 0, NULL);
11363             return TRUE;
11364         }
11365     }
11366 }
11367
11368 /* Save the current position to the given open file and close the file */
11369 int
11370 SavePosition(f, dummy, dummy2)
11371      FILE *f;
11372      int dummy;
11373      char *dummy2;
11374 {
11375     time_t tm;
11376     char *fen;
11377
11378     if (gameMode == EditPosition) EditPositionDone(TRUE);
11379     if (appData.oldSaveStyle) {
11380         tm = time((time_t *) NULL);
11381
11382         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11383         PrintOpponents(f);
11384         fprintf(f, "[--------------\n");
11385         PrintPosition(f, currentMove);
11386         fprintf(f, "--------------]\n");
11387     } else {
11388         fen = PositionToFEN(currentMove, NULL);
11389         fprintf(f, "%s\n", fen);
11390         free(fen);
11391     }
11392     fclose(f);
11393     return TRUE;
11394 }
11395
11396 void
11397 ReloadCmailMsgEvent(unregister)
11398      int unregister;
11399 {
11400 #if !WIN32
11401     static char *inFilename = NULL;
11402     static char *outFilename;
11403     int i;
11404     struct stat inbuf, outbuf;
11405     int status;
11406
11407     /* Any registered moves are unregistered if unregister is set, */
11408     /* i.e. invoked by the signal handler */
11409     if (unregister) {
11410         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11411             cmailMoveRegistered[i] = FALSE;
11412             if (cmailCommentList[i] != NULL) {
11413                 free(cmailCommentList[i]);
11414                 cmailCommentList[i] = NULL;
11415             }
11416         }
11417         nCmailMovesRegistered = 0;
11418     }
11419
11420     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11421         cmailResult[i] = CMAIL_NOT_RESULT;
11422     }
11423     nCmailResults = 0;
11424
11425     if (inFilename == NULL) {
11426         /* Because the filenames are static they only get malloced once  */
11427         /* and they never get freed                                      */
11428         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11429         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11430
11431         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11432         sprintf(outFilename, "%s.out", appData.cmailGameName);
11433     }
11434
11435     status = stat(outFilename, &outbuf);
11436     if (status < 0) {
11437         cmailMailedMove = FALSE;
11438     } else {
11439         status = stat(inFilename, &inbuf);
11440         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11441     }
11442
11443     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11444        counts the games, notes how each one terminated, etc.
11445
11446        It would be nice to remove this kludge and instead gather all
11447        the information while building the game list.  (And to keep it
11448        in the game list nodes instead of having a bunch of fixed-size
11449        parallel arrays.)  Note this will require getting each game's
11450        termination from the PGN tags, as the game list builder does
11451        not process the game moves.  --mann
11452        */
11453     cmailMsgLoaded = TRUE;
11454     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11455
11456     /* Load first game in the file or popup game menu */
11457     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11458
11459 #endif /* !WIN32 */
11460     return;
11461 }
11462
11463 int
11464 RegisterMove()
11465 {
11466     FILE *f;
11467     char string[MSG_SIZ];
11468
11469     if (   cmailMailedMove
11470         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11471         return TRUE;            /* Allow free viewing  */
11472     }
11473
11474     /* Unregister move to ensure that we don't leave RegisterMove        */
11475     /* with the move registered when the conditions for registering no   */
11476     /* longer hold                                                       */
11477     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11478         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11479         nCmailMovesRegistered --;
11480
11481         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11482           {
11483               free(cmailCommentList[lastLoadGameNumber - 1]);
11484               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11485           }
11486     }
11487
11488     if (cmailOldMove == -1) {
11489         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11490         return FALSE;
11491     }
11492
11493     if (currentMove > cmailOldMove + 1) {
11494         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11495         return FALSE;
11496     }
11497
11498     if (currentMove < cmailOldMove) {
11499         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11500         return FALSE;
11501     }
11502
11503     if (forwardMostMove > currentMove) {
11504         /* Silently truncate extra moves */
11505         TruncateGame();
11506     }
11507
11508     if (   (currentMove == cmailOldMove + 1)
11509         || (   (currentMove == cmailOldMove)
11510             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11511                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11512         if (gameInfo.result != GameUnfinished) {
11513             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11514         }
11515
11516         if (commentList[currentMove] != NULL) {
11517             cmailCommentList[lastLoadGameNumber - 1]
11518               = StrSave(commentList[currentMove]);
11519         }
11520         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11521
11522         if (appData.debugMode)
11523           fprintf(debugFP, "Saving %s for game %d\n",
11524                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11525
11526         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11527
11528         f = fopen(string, "w");
11529         if (appData.oldSaveStyle) {
11530             SaveGameOldStyle(f); /* also closes the file */
11531
11532             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11533             f = fopen(string, "w");
11534             SavePosition(f, 0, NULL); /* also closes the file */
11535         } else {
11536             fprintf(f, "{--------------\n");
11537             PrintPosition(f, currentMove);
11538             fprintf(f, "--------------}\n\n");
11539
11540             SaveGame(f, 0, NULL); /* also closes the file*/
11541         }
11542
11543         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11544         nCmailMovesRegistered ++;
11545     } else if (nCmailGames == 1) {
11546         DisplayError(_("You have not made a move yet"), 0);
11547         return FALSE;
11548     }
11549
11550     return TRUE;
11551 }
11552
11553 void
11554 MailMoveEvent()
11555 {
11556 #if !WIN32
11557     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11558     FILE *commandOutput;
11559     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11560     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11561     int nBuffers;
11562     int i;
11563     int archived;
11564     char *arcDir;
11565
11566     if (! cmailMsgLoaded) {
11567         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11568         return;
11569     }
11570
11571     if (nCmailGames == nCmailResults) {
11572         DisplayError(_("No unfinished games"), 0);
11573         return;
11574     }
11575
11576 #if CMAIL_PROHIBIT_REMAIL
11577     if (cmailMailedMove) {
11578       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);
11579         DisplayError(msg, 0);
11580         return;
11581     }
11582 #endif
11583
11584     if (! (cmailMailedMove || RegisterMove())) return;
11585
11586     if (   cmailMailedMove
11587         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11588       snprintf(string, MSG_SIZ, partCommandString,
11589                appData.debugMode ? " -v" : "", appData.cmailGameName);
11590         commandOutput = popen(string, "r");
11591
11592         if (commandOutput == NULL) {
11593             DisplayError(_("Failed to invoke cmail"), 0);
11594         } else {
11595             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11596                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11597             }
11598             if (nBuffers > 1) {
11599                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11600                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11601                 nBytes = MSG_SIZ - 1;
11602             } else {
11603                 (void) memcpy(msg, buffer, nBytes);
11604             }
11605             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11606
11607             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11608                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11609
11610                 archived = TRUE;
11611                 for (i = 0; i < nCmailGames; i ++) {
11612                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11613                         archived = FALSE;
11614                     }
11615                 }
11616                 if (   archived
11617                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11618                         != NULL)) {
11619                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11620                            arcDir,
11621                            appData.cmailGameName,
11622                            gameInfo.date);
11623                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11624                     cmailMsgLoaded = FALSE;
11625                 }
11626             }
11627
11628             DisplayInformation(msg);
11629             pclose(commandOutput);
11630         }
11631     } else {
11632         if ((*cmailMsg) != '\0') {
11633             DisplayInformation(cmailMsg);
11634         }
11635     }
11636
11637     return;
11638 #endif /* !WIN32 */
11639 }
11640
11641 char *
11642 CmailMsg()
11643 {
11644 #if WIN32
11645     return NULL;
11646 #else
11647     int  prependComma = 0;
11648     char number[5];
11649     char string[MSG_SIZ];       /* Space for game-list */
11650     int  i;
11651
11652     if (!cmailMsgLoaded) return "";
11653
11654     if (cmailMailedMove) {
11655       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11656     } else {
11657         /* Create a list of games left */
11658       snprintf(string, MSG_SIZ, "[");
11659         for (i = 0; i < nCmailGames; i ++) {
11660             if (! (   cmailMoveRegistered[i]
11661                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11662                 if (prependComma) {
11663                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11664                 } else {
11665                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11666                     prependComma = 1;
11667                 }
11668
11669                 strcat(string, number);
11670             }
11671         }
11672         strcat(string, "]");
11673
11674         if (nCmailMovesRegistered + nCmailResults == 0) {
11675             switch (nCmailGames) {
11676               case 1:
11677                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11678                 break;
11679
11680               case 2:
11681                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11682                 break;
11683
11684               default:
11685                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11686                          nCmailGames);
11687                 break;
11688             }
11689         } else {
11690             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11691               case 1:
11692                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11693                          string);
11694                 break;
11695
11696               case 0:
11697                 if (nCmailResults == nCmailGames) {
11698                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11699                 } else {
11700                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11701                 }
11702                 break;
11703
11704               default:
11705                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11706                          string);
11707             }
11708         }
11709     }
11710     return cmailMsg;
11711 #endif /* WIN32 */
11712 }
11713
11714 void
11715 ResetGameEvent()
11716 {
11717     if (gameMode == Training)
11718       SetTrainingModeOff();
11719
11720     Reset(TRUE, TRUE);
11721     cmailMsgLoaded = FALSE;
11722     if (appData.icsActive) {
11723       SendToICS(ics_prefix);
11724       SendToICS("refresh\n");
11725     }
11726 }
11727
11728 void
11729 ExitEvent(status)
11730      int status;
11731 {
11732     exiting++;
11733     if (exiting > 2) {
11734       /* Give up on clean exit */
11735       exit(status);
11736     }
11737     if (exiting > 1) {
11738       /* Keep trying for clean exit */
11739       return;
11740     }
11741
11742     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11743
11744     if (telnetISR != NULL) {
11745       RemoveInputSource(telnetISR);
11746     }
11747     if (icsPR != NoProc) {
11748       DestroyChildProcess(icsPR, TRUE);
11749     }
11750
11751     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11752     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11753
11754     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11755     /* make sure this other one finishes before killing it!                  */
11756     if(endingGame) { int count = 0;
11757         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11758         while(endingGame && count++ < 10) DoSleep(1);
11759         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11760     }
11761
11762     /* Kill off chess programs */
11763     if (first.pr != NoProc) {
11764         ExitAnalyzeMode();
11765
11766         DoSleep( appData.delayBeforeQuit );
11767         SendToProgram("quit\n", &first);
11768         DoSleep( appData.delayAfterQuit );
11769         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11770     }
11771     if (second.pr != NoProc) {
11772         DoSleep( appData.delayBeforeQuit );
11773         SendToProgram("quit\n", &second);
11774         DoSleep( appData.delayAfterQuit );
11775         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11776     }
11777     if (first.isr != NULL) {
11778         RemoveInputSource(first.isr);
11779     }
11780     if (second.isr != NULL) {
11781         RemoveInputSource(second.isr);
11782     }
11783
11784     ShutDownFrontEnd();
11785     exit(status);
11786 }
11787
11788 void
11789 PauseEvent()
11790 {
11791     if (appData.debugMode)
11792         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11793     if (pausing) {
11794         pausing = FALSE;
11795         ModeHighlight();
11796         if (gameMode == MachinePlaysWhite ||
11797             gameMode == MachinePlaysBlack) {
11798             StartClocks();
11799         } else {
11800             DisplayBothClocks();
11801         }
11802         if (gameMode == PlayFromGameFile) {
11803             if (appData.timeDelay >= 0)
11804                 AutoPlayGameLoop();
11805         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11806             Reset(FALSE, TRUE);
11807             SendToICS(ics_prefix);
11808             SendToICS("refresh\n");
11809         } else if (currentMove < forwardMostMove) {
11810             ForwardInner(forwardMostMove);
11811         }
11812         pauseExamInvalid = FALSE;
11813     } else {
11814         switch (gameMode) {
11815           default:
11816             return;
11817           case IcsExamining:
11818             pauseExamForwardMostMove = forwardMostMove;
11819             pauseExamInvalid = FALSE;
11820             /* fall through */
11821           case IcsObserving:
11822           case IcsPlayingWhite:
11823           case IcsPlayingBlack:
11824             pausing = TRUE;
11825             ModeHighlight();
11826             return;
11827           case PlayFromGameFile:
11828             (void) StopLoadGameTimer();
11829             pausing = TRUE;
11830             ModeHighlight();
11831             break;
11832           case BeginningOfGame:
11833             if (appData.icsActive) return;
11834             /* else fall through */
11835           case MachinePlaysWhite:
11836           case MachinePlaysBlack:
11837           case TwoMachinesPlay:
11838             if (forwardMostMove == 0)
11839               return;           /* don't pause if no one has moved */
11840             if ((gameMode == MachinePlaysWhite &&
11841                  !WhiteOnMove(forwardMostMove)) ||
11842                 (gameMode == MachinePlaysBlack &&
11843                  WhiteOnMove(forwardMostMove))) {
11844                 StopClocks();
11845             }
11846             pausing = TRUE;
11847             ModeHighlight();
11848             break;
11849         }
11850     }
11851 }
11852
11853 void
11854 EditCommentEvent()
11855 {
11856     char title[MSG_SIZ];
11857
11858     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11859       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11860     } else {
11861       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11862                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11863                parseList[currentMove - 1]);
11864     }
11865
11866     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11867 }
11868
11869
11870 void
11871 EditTagsEvent()
11872 {
11873     char *tags = PGNTags(&gameInfo);
11874     EditTagsPopUp(tags, NULL);
11875     free(tags);
11876 }
11877
11878 void
11879 AnalyzeModeEvent()
11880 {
11881     if (appData.noChessProgram || gameMode == AnalyzeMode)
11882       return;
11883
11884     if (gameMode != AnalyzeFile) {
11885         if (!appData.icsEngineAnalyze) {
11886                EditGameEvent();
11887                if (gameMode != EditGame) return;
11888         }
11889         ResurrectChessProgram();
11890         SendToProgram("analyze\n", &first);
11891         first.analyzing = TRUE;
11892         /*first.maybeThinking = TRUE;*/
11893         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11894         EngineOutputPopUp();
11895     }
11896     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11897     pausing = FALSE;
11898     ModeHighlight();
11899     SetGameInfo();
11900
11901     StartAnalysisClock();
11902     GetTimeMark(&lastNodeCountTime);
11903     lastNodeCount = 0;
11904 }
11905
11906 void
11907 AnalyzeFileEvent()
11908 {
11909     if (appData.noChessProgram || gameMode == AnalyzeFile)
11910       return;
11911
11912     if (gameMode != AnalyzeMode) {
11913         EditGameEvent();
11914         if (gameMode != EditGame) return;
11915         ResurrectChessProgram();
11916         SendToProgram("analyze\n", &first);
11917         first.analyzing = TRUE;
11918         /*first.maybeThinking = TRUE;*/
11919         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11920         EngineOutputPopUp();
11921     }
11922     gameMode = AnalyzeFile;
11923     pausing = FALSE;
11924     ModeHighlight();
11925     SetGameInfo();
11926
11927     StartAnalysisClock();
11928     GetTimeMark(&lastNodeCountTime);
11929     lastNodeCount = 0;
11930 }
11931
11932 void
11933 MachineWhiteEvent()
11934 {
11935     char buf[MSG_SIZ];
11936     char *bookHit = NULL;
11937
11938     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11939       return;
11940
11941
11942     if (gameMode == PlayFromGameFile ||
11943         gameMode == TwoMachinesPlay  ||
11944         gameMode == Training         ||
11945         gameMode == AnalyzeMode      ||
11946         gameMode == EndOfGame)
11947         EditGameEvent();
11948
11949     if (gameMode == EditPosition)
11950         EditPositionDone(TRUE);
11951
11952     if (!WhiteOnMove(currentMove)) {
11953         DisplayError(_("It is not White's turn"), 0);
11954         return;
11955     }
11956
11957     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11958       ExitAnalyzeMode();
11959
11960     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11961         gameMode == AnalyzeFile)
11962         TruncateGame();
11963
11964     ResurrectChessProgram();    /* in case it isn't running */
11965     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11966         gameMode = MachinePlaysWhite;
11967         ResetClocks();
11968     } else
11969     gameMode = MachinePlaysWhite;
11970     pausing = FALSE;
11971     ModeHighlight();
11972     SetGameInfo();
11973     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11974     DisplayTitle(buf);
11975     if (first.sendName) {
11976       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11977       SendToProgram(buf, &first);
11978     }
11979     if (first.sendTime) {
11980       if (first.useColors) {
11981         SendToProgram("black\n", &first); /*gnu kludge*/
11982       }
11983       SendTimeRemaining(&first, TRUE);
11984     }
11985     if (first.useColors) {
11986       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11987     }
11988     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11989     SetMachineThinkingEnables();
11990     first.maybeThinking = TRUE;
11991     StartClocks();
11992     firstMove = FALSE;
11993
11994     if (appData.autoFlipView && !flipView) {
11995       flipView = !flipView;
11996       DrawPosition(FALSE, NULL);
11997       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11998     }
11999
12000     if(bookHit) { // [HGM] book: simulate book reply
12001         static char bookMove[MSG_SIZ]; // a bit generous?
12002
12003         programStats.nodes = programStats.depth = programStats.time =
12004         programStats.score = programStats.got_only_move = 0;
12005         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12006
12007         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12008         strcat(bookMove, bookHit);
12009         HandleMachineMove(bookMove, &first);
12010     }
12011 }
12012
12013 void
12014 MachineBlackEvent()
12015 {
12016   char buf[MSG_SIZ];
12017   char *bookHit = NULL;
12018
12019     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12020         return;
12021
12022
12023     if (gameMode == PlayFromGameFile ||
12024         gameMode == TwoMachinesPlay  ||
12025         gameMode == Training         ||
12026         gameMode == AnalyzeMode      ||
12027         gameMode == EndOfGame)
12028         EditGameEvent();
12029
12030     if (gameMode == EditPosition)
12031         EditPositionDone(TRUE);
12032
12033     if (WhiteOnMove(currentMove)) {
12034         DisplayError(_("It is not Black's turn"), 0);
12035         return;
12036     }
12037
12038     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12039       ExitAnalyzeMode();
12040
12041     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12042         gameMode == AnalyzeFile)
12043         TruncateGame();
12044
12045     ResurrectChessProgram();    /* in case it isn't running */
12046     gameMode = MachinePlaysBlack;
12047     pausing = FALSE;
12048     ModeHighlight();
12049     SetGameInfo();
12050     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12051     DisplayTitle(buf);
12052     if (first.sendName) {
12053       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12054       SendToProgram(buf, &first);
12055     }
12056     if (first.sendTime) {
12057       if (first.useColors) {
12058         SendToProgram("white\n", &first); /*gnu kludge*/
12059       }
12060       SendTimeRemaining(&first, FALSE);
12061     }
12062     if (first.useColors) {
12063       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12064     }
12065     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12066     SetMachineThinkingEnables();
12067     first.maybeThinking = TRUE;
12068     StartClocks();
12069
12070     if (appData.autoFlipView && flipView) {
12071       flipView = !flipView;
12072       DrawPosition(FALSE, NULL);
12073       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12074     }
12075     if(bookHit) { // [HGM] book: simulate book reply
12076         static char bookMove[MSG_SIZ]; // a bit generous?
12077
12078         programStats.nodes = programStats.depth = programStats.time =
12079         programStats.score = programStats.got_only_move = 0;
12080         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12081
12082         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12083         strcat(bookMove, bookHit);
12084         HandleMachineMove(bookMove, &first);
12085     }
12086 }
12087
12088
12089 void
12090 DisplayTwoMachinesTitle()
12091 {
12092     char buf[MSG_SIZ];
12093     if (appData.matchGames > 0) {
12094         if (first.twoMachinesColor[0] == 'w') {
12095           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12096                    gameInfo.white, gameInfo.black,
12097                    first.matchWins, second.matchWins,
12098                    matchGame - 1 - (first.matchWins + second.matchWins));
12099         } else {
12100           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12101                    gameInfo.white, gameInfo.black,
12102                    second.matchWins, first.matchWins,
12103                    matchGame - 1 - (first.matchWins + second.matchWins));
12104         }
12105     } else {
12106       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12107     }
12108     DisplayTitle(buf);
12109 }
12110
12111 void
12112 SettingsMenuIfReady()
12113 {
12114   if (second.lastPing != second.lastPong) {
12115     DisplayMessage("", _("Waiting for second chess program"));
12116     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12117     return;
12118   }
12119   ThawUI();
12120   DisplayMessage("", "");
12121   SettingsPopUp(&second);
12122 }
12123
12124 int
12125 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12126 {
12127     char buf[MSG_SIZ];
12128     if (cps->pr == NULL) {
12129         StartChessProgram(cps);
12130         if (cps->protocolVersion == 1) {
12131           retry();
12132         } else {
12133           /* kludge: allow timeout for initial "feature" command */
12134           FreezeUI();
12135           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12136           DisplayMessage("", buf);
12137           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12138         }
12139         return 1;
12140     }
12141     return 0;
12142 }
12143
12144 void
12145 TwoMachinesEvent P((void))
12146 {
12147     int i;
12148     char buf[MSG_SIZ];
12149     ChessProgramState *onmove;
12150     char *bookHit = NULL;
12151     static int stalling = 0;
12152
12153     if (appData.noChessProgram) return;
12154
12155     switch (gameMode) {
12156       case TwoMachinesPlay:
12157         return;
12158       case MachinePlaysWhite:
12159       case MachinePlaysBlack:
12160         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12161             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12162             return;
12163         }
12164         /* fall through */
12165       case BeginningOfGame:
12166       case PlayFromGameFile:
12167       case EndOfGame:
12168         EditGameEvent();
12169         if (gameMode != EditGame) return;
12170         break;
12171       case EditPosition:
12172         EditPositionDone(TRUE);
12173         break;
12174       case AnalyzeMode:
12175       case AnalyzeFile:
12176         ExitAnalyzeMode();
12177         break;
12178       case EditGame:
12179       default:
12180         break;
12181     }
12182
12183 //    forwardMostMove = currentMove;
12184     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12185     ResurrectChessProgram();    /* in case first program isn't running */
12186
12187     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12188     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12189       DisplayMessage("", _("Waiting for first chess program"));
12190       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12191       return;
12192     }
12193     if(!stalling) {
12194       InitChessProgram(&second, FALSE);
12195       SendToProgram("force\n", &second);
12196     }
12197     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12198       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12199       stalling = 1;
12200       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12201       return;
12202     }
12203     stalling = 0;
12204     DisplayMessage("", "");
12205     if (startedFromSetupPosition) {
12206         SendBoard(&second, backwardMostMove);
12207     if (appData.debugMode) {
12208         fprintf(debugFP, "Two Machines\n");
12209     }
12210     }
12211     for (i = backwardMostMove; i < forwardMostMove; i++) {
12212         SendMoveToProgram(i, &second);
12213     }
12214
12215     gameMode = TwoMachinesPlay;
12216     pausing = FALSE;
12217     ModeHighlight();
12218     SetGameInfo();
12219     DisplayTwoMachinesTitle();
12220     firstMove = TRUE;
12221     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12222         onmove = &first;
12223     } else {
12224         onmove = &second;
12225     }
12226
12227     SendToProgram(first.computerString, &first);
12228     if (first.sendName) {
12229       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12230       SendToProgram(buf, &first);
12231     }
12232     SendToProgram(second.computerString, &second);
12233     if (second.sendName) {
12234       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12235       SendToProgram(buf, &second);
12236     }
12237
12238     ResetClocks();
12239     if (!first.sendTime || !second.sendTime) {
12240         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12241         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12242     }
12243     if (onmove->sendTime) {
12244       if (onmove->useColors) {
12245         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12246       }
12247       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12248     }
12249     if (onmove->useColors) {
12250       SendToProgram(onmove->twoMachinesColor, onmove);
12251     }
12252     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12253 //    SendToProgram("go\n", onmove);
12254     onmove->maybeThinking = TRUE;
12255     SetMachineThinkingEnables();
12256
12257     StartClocks();
12258
12259     if(bookHit) { // [HGM] book: simulate book reply
12260         static char bookMove[MSG_SIZ]; // a bit generous?
12261
12262         programStats.nodes = programStats.depth = programStats.time =
12263         programStats.score = programStats.got_only_move = 0;
12264         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12265
12266         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12267         strcat(bookMove, bookHit);
12268         savedMessage = bookMove; // args for deferred call
12269         savedState = onmove;
12270         ScheduleDelayedEvent(DeferredBookMove, 1);
12271     }
12272 }
12273
12274 void
12275 TrainingEvent()
12276 {
12277     if (gameMode == Training) {
12278       SetTrainingModeOff();
12279       gameMode = PlayFromGameFile;
12280       DisplayMessage("", _("Training mode off"));
12281     } else {
12282       gameMode = Training;
12283       animateTraining = appData.animate;
12284
12285       /* make sure we are not already at the end of the game */
12286       if (currentMove < forwardMostMove) {
12287         SetTrainingModeOn();
12288         DisplayMessage("", _("Training mode on"));
12289       } else {
12290         gameMode = PlayFromGameFile;
12291         DisplayError(_("Already at end of game"), 0);
12292       }
12293     }
12294     ModeHighlight();
12295 }
12296
12297 void
12298 IcsClientEvent()
12299 {
12300     if (!appData.icsActive) return;
12301     switch (gameMode) {
12302       case IcsPlayingWhite:
12303       case IcsPlayingBlack:
12304       case IcsObserving:
12305       case IcsIdle:
12306       case BeginningOfGame:
12307       case IcsExamining:
12308         return;
12309
12310       case EditGame:
12311         break;
12312
12313       case EditPosition:
12314         EditPositionDone(TRUE);
12315         break;
12316
12317       case AnalyzeMode:
12318       case AnalyzeFile:
12319         ExitAnalyzeMode();
12320         break;
12321
12322       default:
12323         EditGameEvent();
12324         break;
12325     }
12326
12327     gameMode = IcsIdle;
12328     ModeHighlight();
12329     return;
12330 }
12331
12332
12333 void
12334 EditGameEvent()
12335 {
12336     int i;
12337
12338     switch (gameMode) {
12339       case Training:
12340         SetTrainingModeOff();
12341         break;
12342       case MachinePlaysWhite:
12343       case MachinePlaysBlack:
12344       case BeginningOfGame:
12345         SendToProgram("force\n", &first);
12346         SetUserThinkingEnables();
12347         break;
12348       case PlayFromGameFile:
12349         (void) StopLoadGameTimer();
12350         if (gameFileFP != NULL) {
12351             gameFileFP = NULL;
12352         }
12353         break;
12354       case EditPosition:
12355         EditPositionDone(TRUE);
12356         break;
12357       case AnalyzeMode:
12358       case AnalyzeFile:
12359         ExitAnalyzeMode();
12360         SendToProgram("force\n", &first);
12361         break;
12362       case TwoMachinesPlay:
12363         GameEnds(EndOfFile, NULL, GE_PLAYER);
12364         ResurrectChessProgram();
12365         SetUserThinkingEnables();
12366         break;
12367       case EndOfGame:
12368         ResurrectChessProgram();
12369         break;
12370       case IcsPlayingBlack:
12371       case IcsPlayingWhite:
12372         DisplayError(_("Warning: You are still playing a game"), 0);
12373         break;
12374       case IcsObserving:
12375         DisplayError(_("Warning: You are still observing a game"), 0);
12376         break;
12377       case IcsExamining:
12378         DisplayError(_("Warning: You are still examining a game"), 0);
12379         break;
12380       case IcsIdle:
12381         break;
12382       case EditGame:
12383       default:
12384         return;
12385     }
12386
12387     pausing = FALSE;
12388     StopClocks();
12389     first.offeredDraw = second.offeredDraw = 0;
12390
12391     if (gameMode == PlayFromGameFile) {
12392         whiteTimeRemaining = timeRemaining[0][currentMove];
12393         blackTimeRemaining = timeRemaining[1][currentMove];
12394         DisplayTitle("");
12395     }
12396
12397     if (gameMode == MachinePlaysWhite ||
12398         gameMode == MachinePlaysBlack ||
12399         gameMode == TwoMachinesPlay ||
12400         gameMode == EndOfGame) {
12401         i = forwardMostMove;
12402         while (i > currentMove) {
12403             SendToProgram("undo\n", &first);
12404             i--;
12405         }
12406         whiteTimeRemaining = timeRemaining[0][currentMove];
12407         blackTimeRemaining = timeRemaining[1][currentMove];
12408         DisplayBothClocks();
12409         if (whiteFlag || blackFlag) {
12410             whiteFlag = blackFlag = 0;
12411         }
12412         DisplayTitle("");
12413     }
12414
12415     gameMode = EditGame;
12416     ModeHighlight();
12417     SetGameInfo();
12418 }
12419
12420
12421 void
12422 EditPositionEvent()
12423 {
12424     if (gameMode == EditPosition) {
12425         EditGameEvent();
12426         return;
12427     }
12428
12429     EditGameEvent();
12430     if (gameMode != EditGame) return;
12431
12432     gameMode = EditPosition;
12433     ModeHighlight();
12434     SetGameInfo();
12435     if (currentMove > 0)
12436       CopyBoard(boards[0], boards[currentMove]);
12437
12438     blackPlaysFirst = !WhiteOnMove(currentMove);
12439     ResetClocks();
12440     currentMove = forwardMostMove = backwardMostMove = 0;
12441     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12442     DisplayMove(-1);
12443 }
12444
12445 void
12446 ExitAnalyzeMode()
12447 {
12448     /* [DM] icsEngineAnalyze - possible call from other functions */
12449     if (appData.icsEngineAnalyze) {
12450         appData.icsEngineAnalyze = FALSE;
12451
12452         DisplayMessage("",_("Close ICS engine analyze..."));
12453     }
12454     if (first.analysisSupport && first.analyzing) {
12455       SendToProgram("exit\n", &first);
12456       first.analyzing = FALSE;
12457     }
12458     thinkOutput[0] = NULLCHAR;
12459 }
12460
12461 void
12462 EditPositionDone(Boolean fakeRights)
12463 {
12464     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12465
12466     startedFromSetupPosition = TRUE;
12467     InitChessProgram(&first, FALSE);
12468     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12469       boards[0][EP_STATUS] = EP_NONE;
12470       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12471     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12472         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12473         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12474       } else boards[0][CASTLING][2] = NoRights;
12475     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12476         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12477         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12478       } else boards[0][CASTLING][5] = NoRights;
12479     }
12480     SendToProgram("force\n", &first);
12481     if (blackPlaysFirst) {
12482         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12483         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12484         currentMove = forwardMostMove = backwardMostMove = 1;
12485         CopyBoard(boards[1], boards[0]);
12486     } else {
12487         currentMove = forwardMostMove = backwardMostMove = 0;
12488     }
12489     SendBoard(&first, forwardMostMove);
12490     if (appData.debugMode) {
12491         fprintf(debugFP, "EditPosDone\n");
12492     }
12493     DisplayTitle("");
12494     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12495     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12496     gameMode = EditGame;
12497     ModeHighlight();
12498     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12499     ClearHighlights(); /* [AS] */
12500 }
12501
12502 /* Pause for `ms' milliseconds */
12503 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12504 void
12505 TimeDelay(ms)
12506      long ms;
12507 {
12508     TimeMark m1, m2;
12509
12510     GetTimeMark(&m1);
12511     do {
12512         GetTimeMark(&m2);
12513     } while (SubtractTimeMarks(&m2, &m1) < ms);
12514 }
12515
12516 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12517 void
12518 SendMultiLineToICS(buf)
12519      char *buf;
12520 {
12521     char temp[MSG_SIZ+1], *p;
12522     int len;
12523
12524     len = strlen(buf);
12525     if (len > MSG_SIZ)
12526       len = MSG_SIZ;
12527
12528     strncpy(temp, buf, len);
12529     temp[len] = 0;
12530
12531     p = temp;
12532     while (*p) {
12533         if (*p == '\n' || *p == '\r')
12534           *p = ' ';
12535         ++p;
12536     }
12537
12538     strcat(temp, "\n");
12539     SendToICS(temp);
12540     SendToPlayer(temp, strlen(temp));
12541 }
12542
12543 void
12544 SetWhiteToPlayEvent()
12545 {
12546     if (gameMode == EditPosition) {
12547         blackPlaysFirst = FALSE;
12548         DisplayBothClocks();    /* works because currentMove is 0 */
12549     } else if (gameMode == IcsExamining) {
12550         SendToICS(ics_prefix);
12551         SendToICS("tomove white\n");
12552     }
12553 }
12554
12555 void
12556 SetBlackToPlayEvent()
12557 {
12558     if (gameMode == EditPosition) {
12559         blackPlaysFirst = TRUE;
12560         currentMove = 1;        /* kludge */
12561         DisplayBothClocks();
12562         currentMove = 0;
12563     } else if (gameMode == IcsExamining) {
12564         SendToICS(ics_prefix);
12565         SendToICS("tomove black\n");
12566     }
12567 }
12568
12569 void
12570 EditPositionMenuEvent(selection, x, y)
12571      ChessSquare selection;
12572      int x, y;
12573 {
12574     char buf[MSG_SIZ];
12575     ChessSquare piece = boards[0][y][x];
12576
12577     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12578
12579     switch (selection) {
12580       case ClearBoard:
12581         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12582             SendToICS(ics_prefix);
12583             SendToICS("bsetup clear\n");
12584         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12585             SendToICS(ics_prefix);
12586             SendToICS("clearboard\n");
12587         } else {
12588             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12589                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12590                 for (y = 0; y < BOARD_HEIGHT; y++) {
12591                     if (gameMode == IcsExamining) {
12592                         if (boards[currentMove][y][x] != EmptySquare) {
12593                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12594                                     AAA + x, ONE + y);
12595                             SendToICS(buf);
12596                         }
12597                     } else {
12598                         boards[0][y][x] = p;
12599                     }
12600                 }
12601             }
12602         }
12603         if (gameMode == EditPosition) {
12604             DrawPosition(FALSE, boards[0]);
12605         }
12606         break;
12607
12608       case WhitePlay:
12609         SetWhiteToPlayEvent();
12610         break;
12611
12612       case BlackPlay:
12613         SetBlackToPlayEvent();
12614         break;
12615
12616       case EmptySquare:
12617         if (gameMode == IcsExamining) {
12618             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12619             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12620             SendToICS(buf);
12621         } else {
12622             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12623                 if(x == BOARD_LEFT-2) {
12624                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12625                     boards[0][y][1] = 0;
12626                 } else
12627                 if(x == BOARD_RGHT+1) {
12628                     if(y >= gameInfo.holdingsSize) break;
12629                     boards[0][y][BOARD_WIDTH-2] = 0;
12630                 } else break;
12631             }
12632             boards[0][y][x] = EmptySquare;
12633             DrawPosition(FALSE, boards[0]);
12634         }
12635         break;
12636
12637       case PromotePiece:
12638         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12639            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12640             selection = (ChessSquare) (PROMOTED piece);
12641         } else if(piece == EmptySquare) selection = WhiteSilver;
12642         else selection = (ChessSquare)((int)piece - 1);
12643         goto defaultlabel;
12644
12645       case DemotePiece:
12646         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12647            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12648             selection = (ChessSquare) (DEMOTED piece);
12649         } else if(piece == EmptySquare) selection = BlackSilver;
12650         else selection = (ChessSquare)((int)piece + 1);
12651         goto defaultlabel;
12652
12653       case WhiteQueen:
12654       case BlackQueen:
12655         if(gameInfo.variant == VariantShatranj ||
12656            gameInfo.variant == VariantXiangqi  ||
12657            gameInfo.variant == VariantCourier  ||
12658            gameInfo.variant == VariantMakruk     )
12659             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12660         goto defaultlabel;
12661
12662       case WhiteKing:
12663       case BlackKing:
12664         if(gameInfo.variant == VariantXiangqi)
12665             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12666         if(gameInfo.variant == VariantKnightmate)
12667             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12668       default:
12669         defaultlabel:
12670         if (gameMode == IcsExamining) {
12671             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12672             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12673                      PieceToChar(selection), AAA + x, ONE + y);
12674             SendToICS(buf);
12675         } else {
12676             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12677                 int n;
12678                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12679                     n = PieceToNumber(selection - BlackPawn);
12680                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12681                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12682                     boards[0][BOARD_HEIGHT-1-n][1]++;
12683                 } else
12684                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12685                     n = PieceToNumber(selection);
12686                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12687                     boards[0][n][BOARD_WIDTH-1] = selection;
12688                     boards[0][n][BOARD_WIDTH-2]++;
12689                 }
12690             } else
12691             boards[0][y][x] = selection;
12692             DrawPosition(TRUE, boards[0]);
12693         }
12694         break;
12695     }
12696 }
12697
12698
12699 void
12700 DropMenuEvent(selection, x, y)
12701      ChessSquare selection;
12702      int x, y;
12703 {
12704     ChessMove moveType;
12705
12706     switch (gameMode) {
12707       case IcsPlayingWhite:
12708       case MachinePlaysBlack:
12709         if (!WhiteOnMove(currentMove)) {
12710             DisplayMoveError(_("It is Black's turn"));
12711             return;
12712         }
12713         moveType = WhiteDrop;
12714         break;
12715       case IcsPlayingBlack:
12716       case MachinePlaysWhite:
12717         if (WhiteOnMove(currentMove)) {
12718             DisplayMoveError(_("It is White's turn"));
12719             return;
12720         }
12721         moveType = BlackDrop;
12722         break;
12723       case EditGame:
12724         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12725         break;
12726       default:
12727         return;
12728     }
12729
12730     if (moveType == BlackDrop && selection < BlackPawn) {
12731       selection = (ChessSquare) ((int) selection
12732                                  + (int) BlackPawn - (int) WhitePawn);
12733     }
12734     if (boards[currentMove][y][x] != EmptySquare) {
12735         DisplayMoveError(_("That square is occupied"));
12736         return;
12737     }
12738
12739     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12740 }
12741
12742 void
12743 AcceptEvent()
12744 {
12745     /* Accept a pending offer of any kind from opponent */
12746
12747     if (appData.icsActive) {
12748         SendToICS(ics_prefix);
12749         SendToICS("accept\n");
12750     } else if (cmailMsgLoaded) {
12751         if (currentMove == cmailOldMove &&
12752             commentList[cmailOldMove] != NULL &&
12753             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12754                    "Black offers a draw" : "White offers a draw")) {
12755             TruncateGame();
12756             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12757             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12758         } else {
12759             DisplayError(_("There is no pending offer on this move"), 0);
12760             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12761         }
12762     } else {
12763         /* Not used for offers from chess program */
12764     }
12765 }
12766
12767 void
12768 DeclineEvent()
12769 {
12770     /* Decline a pending offer of any kind from opponent */
12771
12772     if (appData.icsActive) {
12773         SendToICS(ics_prefix);
12774         SendToICS("decline\n");
12775     } else if (cmailMsgLoaded) {
12776         if (currentMove == cmailOldMove &&
12777             commentList[cmailOldMove] != NULL &&
12778             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12779                    "Black offers a draw" : "White offers a draw")) {
12780 #ifdef NOTDEF
12781             AppendComment(cmailOldMove, "Draw declined", TRUE);
12782             DisplayComment(cmailOldMove - 1, "Draw declined");
12783 #endif /*NOTDEF*/
12784         } else {
12785             DisplayError(_("There is no pending offer on this move"), 0);
12786         }
12787     } else {
12788         /* Not used for offers from chess program */
12789     }
12790 }
12791
12792 void
12793 RematchEvent()
12794 {
12795     /* Issue ICS rematch command */
12796     if (appData.icsActive) {
12797         SendToICS(ics_prefix);
12798         SendToICS("rematch\n");
12799     }
12800 }
12801
12802 void
12803 CallFlagEvent()
12804 {
12805     /* Call your opponent's flag (claim a win on time) */
12806     if (appData.icsActive) {
12807         SendToICS(ics_prefix);
12808         SendToICS("flag\n");
12809     } else {
12810         switch (gameMode) {
12811           default:
12812             return;
12813           case MachinePlaysWhite:
12814             if (whiteFlag) {
12815                 if (blackFlag)
12816                   GameEnds(GameIsDrawn, "Both players ran out of time",
12817                            GE_PLAYER);
12818                 else
12819                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12820             } else {
12821                 DisplayError(_("Your opponent is not out of time"), 0);
12822             }
12823             break;
12824           case MachinePlaysBlack:
12825             if (blackFlag) {
12826                 if (whiteFlag)
12827                   GameEnds(GameIsDrawn, "Both players ran out of time",
12828                            GE_PLAYER);
12829                 else
12830                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12831             } else {
12832                 DisplayError(_("Your opponent is not out of time"), 0);
12833             }
12834             break;
12835         }
12836     }
12837 }
12838
12839 void
12840 ClockClick(int which)
12841 {       // [HGM] code moved to back-end from winboard.c
12842         if(which) { // black clock
12843           if (gameMode == EditPosition || gameMode == IcsExamining) {
12844             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12845             SetBlackToPlayEvent();
12846           } else if (gameMode == EditGame || shiftKey) {
12847             AdjustClock(which, -1);
12848           } else if (gameMode == IcsPlayingWhite ||
12849                      gameMode == MachinePlaysBlack) {
12850             CallFlagEvent();
12851           }
12852         } else { // white clock
12853           if (gameMode == EditPosition || gameMode == IcsExamining) {
12854             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12855             SetWhiteToPlayEvent();
12856           } else if (gameMode == EditGame || shiftKey) {
12857             AdjustClock(which, -1);
12858           } else if (gameMode == IcsPlayingBlack ||
12859                    gameMode == MachinePlaysWhite) {
12860             CallFlagEvent();
12861           }
12862         }
12863 }
12864
12865 void
12866 DrawEvent()
12867 {
12868     /* Offer draw or accept pending draw offer from opponent */
12869
12870     if (appData.icsActive) {
12871         /* Note: tournament rules require draw offers to be
12872            made after you make your move but before you punch
12873            your clock.  Currently ICS doesn't let you do that;
12874            instead, you immediately punch your clock after making
12875            a move, but you can offer a draw at any time. */
12876
12877         SendToICS(ics_prefix);
12878         SendToICS("draw\n");
12879         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12880     } else if (cmailMsgLoaded) {
12881         if (currentMove == cmailOldMove &&
12882             commentList[cmailOldMove] != NULL &&
12883             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12884                    "Black offers a draw" : "White offers a draw")) {
12885             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12886             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12887         } else if (currentMove == cmailOldMove + 1) {
12888             char *offer = WhiteOnMove(cmailOldMove) ?
12889               "White offers a draw" : "Black offers a draw";
12890             AppendComment(currentMove, offer, TRUE);
12891             DisplayComment(currentMove - 1, offer);
12892             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12893         } else {
12894             DisplayError(_("You must make your move before offering a draw"), 0);
12895             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12896         }
12897     } else if (first.offeredDraw) {
12898         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12899     } else {
12900         if (first.sendDrawOffers) {
12901             SendToProgram("draw\n", &first);
12902             userOfferedDraw = TRUE;
12903         }
12904     }
12905 }
12906
12907 void
12908 AdjournEvent()
12909 {
12910     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12911
12912     if (appData.icsActive) {
12913         SendToICS(ics_prefix);
12914         SendToICS("adjourn\n");
12915     } else {
12916         /* Currently GNU Chess doesn't offer or accept Adjourns */
12917     }
12918 }
12919
12920
12921 void
12922 AbortEvent()
12923 {
12924     /* Offer Abort or accept pending Abort offer from opponent */
12925
12926     if (appData.icsActive) {
12927         SendToICS(ics_prefix);
12928         SendToICS("abort\n");
12929     } else {
12930         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12931     }
12932 }
12933
12934 void
12935 ResignEvent()
12936 {
12937     /* Resign.  You can do this even if it's not your turn. */
12938
12939     if (appData.icsActive) {
12940         SendToICS(ics_prefix);
12941         SendToICS("resign\n");
12942     } else {
12943         switch (gameMode) {
12944           case MachinePlaysWhite:
12945             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12946             break;
12947           case MachinePlaysBlack:
12948             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12949             break;
12950           case EditGame:
12951             if (cmailMsgLoaded) {
12952                 TruncateGame();
12953                 if (WhiteOnMove(cmailOldMove)) {
12954                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12955                 } else {
12956                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12957                 }
12958                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12959             }
12960             break;
12961           default:
12962             break;
12963         }
12964     }
12965 }
12966
12967
12968 void
12969 StopObservingEvent()
12970 {
12971     /* Stop observing current games */
12972     SendToICS(ics_prefix);
12973     SendToICS("unobserve\n");
12974 }
12975
12976 void
12977 StopExaminingEvent()
12978 {
12979     /* Stop observing current game */
12980     SendToICS(ics_prefix);
12981     SendToICS("unexamine\n");
12982 }
12983
12984 void
12985 ForwardInner(target)
12986      int target;
12987 {
12988     int limit;
12989
12990     if (appData.debugMode)
12991         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12992                 target, currentMove, forwardMostMove);
12993
12994     if (gameMode == EditPosition)
12995       return;
12996
12997     if (gameMode == PlayFromGameFile && !pausing)
12998       PauseEvent();
12999
13000     if (gameMode == IcsExamining && pausing)
13001       limit = pauseExamForwardMostMove;
13002     else
13003       limit = forwardMostMove;
13004
13005     if (target > limit) target = limit;
13006
13007     if (target > 0 && moveList[target - 1][0]) {
13008         int fromX, fromY, toX, toY;
13009         toX = moveList[target - 1][2] - AAA;
13010         toY = moveList[target - 1][3] - ONE;
13011         if (moveList[target - 1][1] == '@') {
13012             if (appData.highlightLastMove) {
13013                 SetHighlights(-1, -1, toX, toY);
13014             }
13015         } else {
13016             fromX = moveList[target - 1][0] - AAA;
13017             fromY = moveList[target - 1][1] - ONE;
13018             if (target == currentMove + 1) {
13019                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13020             }
13021             if (appData.highlightLastMove) {
13022                 SetHighlights(fromX, fromY, toX, toY);
13023             }
13024         }
13025     }
13026     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13027         gameMode == Training || gameMode == PlayFromGameFile ||
13028         gameMode == AnalyzeFile) {
13029         while (currentMove < target) {
13030             SendMoveToProgram(currentMove++, &first);
13031         }
13032     } else {
13033         currentMove = target;
13034     }
13035
13036     if (gameMode == EditGame || gameMode == EndOfGame) {
13037         whiteTimeRemaining = timeRemaining[0][currentMove];
13038         blackTimeRemaining = timeRemaining[1][currentMove];
13039     }
13040     DisplayBothClocks();
13041     DisplayMove(currentMove - 1);
13042     DrawPosition(FALSE, boards[currentMove]);
13043     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13044     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13045         DisplayComment(currentMove - 1, commentList[currentMove]);
13046     }
13047 }
13048
13049
13050 void
13051 ForwardEvent()
13052 {
13053     if (gameMode == IcsExamining && !pausing) {
13054         SendToICS(ics_prefix);
13055         SendToICS("forward\n");
13056     } else {
13057         ForwardInner(currentMove + 1);
13058     }
13059 }
13060
13061 void
13062 ToEndEvent()
13063 {
13064     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13065         /* to optimze, we temporarily turn off analysis mode while we feed
13066          * the remaining moves to the engine. Otherwise we get analysis output
13067          * after each move.
13068          */
13069         if (first.analysisSupport) {
13070           SendToProgram("exit\nforce\n", &first);
13071           first.analyzing = FALSE;
13072         }
13073     }
13074
13075     if (gameMode == IcsExamining && !pausing) {
13076         SendToICS(ics_prefix);
13077         SendToICS("forward 999999\n");
13078     } else {
13079         ForwardInner(forwardMostMove);
13080     }
13081
13082     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13083         /* we have fed all the moves, so reactivate analysis mode */
13084         SendToProgram("analyze\n", &first);
13085         first.analyzing = TRUE;
13086         /*first.maybeThinking = TRUE;*/
13087         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13088     }
13089 }
13090
13091 void
13092 BackwardInner(target)
13093      int target;
13094 {
13095     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13096
13097     if (appData.debugMode)
13098         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13099                 target, currentMove, forwardMostMove);
13100
13101     if (gameMode == EditPosition) return;
13102     if (currentMove <= backwardMostMove) {
13103         ClearHighlights();
13104         DrawPosition(full_redraw, boards[currentMove]);
13105         return;
13106     }
13107     if (gameMode == PlayFromGameFile && !pausing)
13108       PauseEvent();
13109
13110     if (moveList[target][0]) {
13111         int fromX, fromY, toX, toY;
13112         toX = moveList[target][2] - AAA;
13113         toY = moveList[target][3] - ONE;
13114         if (moveList[target][1] == '@') {
13115             if (appData.highlightLastMove) {
13116                 SetHighlights(-1, -1, toX, toY);
13117             }
13118         } else {
13119             fromX = moveList[target][0] - AAA;
13120             fromY = moveList[target][1] - ONE;
13121             if (target == currentMove - 1) {
13122                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13123             }
13124             if (appData.highlightLastMove) {
13125                 SetHighlights(fromX, fromY, toX, toY);
13126             }
13127         }
13128     }
13129     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13130         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13131         while (currentMove > target) {
13132             SendToProgram("undo\n", &first);
13133             currentMove--;
13134         }
13135     } else {
13136         currentMove = target;
13137     }
13138
13139     if (gameMode == EditGame || gameMode == EndOfGame) {
13140         whiteTimeRemaining = timeRemaining[0][currentMove];
13141         blackTimeRemaining = timeRemaining[1][currentMove];
13142     }
13143     DisplayBothClocks();
13144     DisplayMove(currentMove - 1);
13145     DrawPosition(full_redraw, boards[currentMove]);
13146     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13147     // [HGM] PV info: routine tests if comment empty
13148     DisplayComment(currentMove - 1, commentList[currentMove]);
13149 }
13150
13151 void
13152 BackwardEvent()
13153 {
13154     if (gameMode == IcsExamining && !pausing) {
13155         SendToICS(ics_prefix);
13156         SendToICS("backward\n");
13157     } else {
13158         BackwardInner(currentMove - 1);
13159     }
13160 }
13161
13162 void
13163 ToStartEvent()
13164 {
13165     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13166         /* to optimize, we temporarily turn off analysis mode while we undo
13167          * all the moves. Otherwise we get analysis output after each undo.
13168          */
13169         if (first.analysisSupport) {
13170           SendToProgram("exit\nforce\n", &first);
13171           first.analyzing = FALSE;
13172         }
13173     }
13174
13175     if (gameMode == IcsExamining && !pausing) {
13176         SendToICS(ics_prefix);
13177         SendToICS("backward 999999\n");
13178     } else {
13179         BackwardInner(backwardMostMove);
13180     }
13181
13182     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13183         /* we have fed all the moves, so reactivate analysis mode */
13184         SendToProgram("analyze\n", &first);
13185         first.analyzing = TRUE;
13186         /*first.maybeThinking = TRUE;*/
13187         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13188     }
13189 }
13190
13191 void
13192 ToNrEvent(int to)
13193 {
13194   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13195   if (to >= forwardMostMove) to = forwardMostMove;
13196   if (to <= backwardMostMove) to = backwardMostMove;
13197   if (to < currentMove) {
13198     BackwardInner(to);
13199   } else {
13200     ForwardInner(to);
13201   }
13202 }
13203
13204 void
13205 RevertEvent(Boolean annotate)
13206 {
13207     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13208         return;
13209     }
13210     if (gameMode != IcsExamining) {
13211         DisplayError(_("You are not examining a game"), 0);
13212         return;
13213     }
13214     if (pausing) {
13215         DisplayError(_("You can't revert while pausing"), 0);
13216         return;
13217     }
13218     SendToICS(ics_prefix);
13219     SendToICS("revert\n");
13220 }
13221
13222 void
13223 RetractMoveEvent()
13224 {
13225     switch (gameMode) {
13226       case MachinePlaysWhite:
13227       case MachinePlaysBlack:
13228         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13229             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13230             return;
13231         }
13232         if (forwardMostMove < 2) return;
13233         currentMove = forwardMostMove = forwardMostMove - 2;
13234         whiteTimeRemaining = timeRemaining[0][currentMove];
13235         blackTimeRemaining = timeRemaining[1][currentMove];
13236         DisplayBothClocks();
13237         DisplayMove(currentMove - 1);
13238         ClearHighlights();/*!! could figure this out*/
13239         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13240         SendToProgram("remove\n", &first);
13241         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13242         break;
13243
13244       case BeginningOfGame:
13245       default:
13246         break;
13247
13248       case IcsPlayingWhite:
13249       case IcsPlayingBlack:
13250         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13251             SendToICS(ics_prefix);
13252             SendToICS("takeback 2\n");
13253         } else {
13254             SendToICS(ics_prefix);
13255             SendToICS("takeback 1\n");
13256         }
13257         break;
13258     }
13259 }
13260
13261 void
13262 MoveNowEvent()
13263 {
13264     ChessProgramState *cps;
13265
13266     switch (gameMode) {
13267       case MachinePlaysWhite:
13268         if (!WhiteOnMove(forwardMostMove)) {
13269             DisplayError(_("It is your turn"), 0);
13270             return;
13271         }
13272         cps = &first;
13273         break;
13274       case MachinePlaysBlack:
13275         if (WhiteOnMove(forwardMostMove)) {
13276             DisplayError(_("It is your turn"), 0);
13277             return;
13278         }
13279         cps = &first;
13280         break;
13281       case TwoMachinesPlay:
13282         if (WhiteOnMove(forwardMostMove) ==
13283             (first.twoMachinesColor[0] == 'w')) {
13284             cps = &first;
13285         } else {
13286             cps = &second;
13287         }
13288         break;
13289       case BeginningOfGame:
13290       default:
13291         return;
13292     }
13293     SendToProgram("?\n", cps);
13294 }
13295
13296 void
13297 TruncateGameEvent()
13298 {
13299     EditGameEvent();
13300     if (gameMode != EditGame) return;
13301     TruncateGame();
13302 }
13303
13304 void
13305 TruncateGame()
13306 {
13307     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13308     if (forwardMostMove > currentMove) {
13309         if (gameInfo.resultDetails != NULL) {
13310             free(gameInfo.resultDetails);
13311             gameInfo.resultDetails = NULL;
13312             gameInfo.result = GameUnfinished;
13313         }
13314         forwardMostMove = currentMove;
13315         HistorySet(parseList, backwardMostMove, forwardMostMove,
13316                    currentMove-1);
13317     }
13318 }
13319
13320 void
13321 HintEvent()
13322 {
13323     if (appData.noChessProgram) return;
13324     switch (gameMode) {
13325       case MachinePlaysWhite:
13326         if (WhiteOnMove(forwardMostMove)) {
13327             DisplayError(_("Wait until your turn"), 0);
13328             return;
13329         }
13330         break;
13331       case BeginningOfGame:
13332       case MachinePlaysBlack:
13333         if (!WhiteOnMove(forwardMostMove)) {
13334             DisplayError(_("Wait until your turn"), 0);
13335             return;
13336         }
13337         break;
13338       default:
13339         DisplayError(_("No hint available"), 0);
13340         return;
13341     }
13342     SendToProgram("hint\n", &first);
13343     hintRequested = TRUE;
13344 }
13345
13346 void
13347 BookEvent()
13348 {
13349     if (appData.noChessProgram) return;
13350     switch (gameMode) {
13351       case MachinePlaysWhite:
13352         if (WhiteOnMove(forwardMostMove)) {
13353             DisplayError(_("Wait until your turn"), 0);
13354             return;
13355         }
13356         break;
13357       case BeginningOfGame:
13358       case MachinePlaysBlack:
13359         if (!WhiteOnMove(forwardMostMove)) {
13360             DisplayError(_("Wait until your turn"), 0);
13361             return;
13362         }
13363         break;
13364       case EditPosition:
13365         EditPositionDone(TRUE);
13366         break;
13367       case TwoMachinesPlay:
13368         return;
13369       default:
13370         break;
13371     }
13372     SendToProgram("bk\n", &first);
13373     bookOutput[0] = NULLCHAR;
13374     bookRequested = TRUE;
13375 }
13376
13377 void
13378 AboutGameEvent()
13379 {
13380     char *tags = PGNTags(&gameInfo);
13381     TagsPopUp(tags, CmailMsg());
13382     free(tags);
13383 }
13384
13385 /* end button procedures */
13386
13387 void
13388 PrintPosition(fp, move)
13389      FILE *fp;
13390      int move;
13391 {
13392     int i, j;
13393
13394     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13395         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13396             char c = PieceToChar(boards[move][i][j]);
13397             fputc(c == 'x' ? '.' : c, fp);
13398             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13399         }
13400     }
13401     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13402       fprintf(fp, "white to play\n");
13403     else
13404       fprintf(fp, "black to play\n");
13405 }
13406
13407 void
13408 PrintOpponents(fp)
13409      FILE *fp;
13410 {
13411     if (gameInfo.white != NULL) {
13412         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13413     } else {
13414         fprintf(fp, "\n");
13415     }
13416 }
13417
13418 /* Find last component of program's own name, using some heuristics */
13419 void
13420 TidyProgramName(prog, host, buf)
13421      char *prog, *host, buf[MSG_SIZ];
13422 {
13423     char *p, *q;
13424     int local = (strcmp(host, "localhost") == 0);
13425     while (!local && (p = strchr(prog, ';')) != NULL) {
13426         p++;
13427         while (*p == ' ') p++;
13428         prog = p;
13429     }
13430     if (*prog == '"' || *prog == '\'') {
13431         q = strchr(prog + 1, *prog);
13432     } else {
13433         q = strchr(prog, ' ');
13434     }
13435     if (q == NULL) q = prog + strlen(prog);
13436     p = q;
13437     while (p >= prog && *p != '/' && *p != '\\') p--;
13438     p++;
13439     if(p == prog && *p == '"') p++;
13440     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13441     memcpy(buf, p, q - p);
13442     buf[q - p] = NULLCHAR;
13443     if (!local) {
13444         strcat(buf, "@");
13445         strcat(buf, host);
13446     }
13447 }
13448
13449 char *
13450 TimeControlTagValue()
13451 {
13452     char buf[MSG_SIZ];
13453     if (!appData.clockMode) {
13454       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13455     } else if (movesPerSession > 0) {
13456       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13457     } else if (timeIncrement == 0) {
13458       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13459     } else {
13460       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13461     }
13462     return StrSave(buf);
13463 }
13464
13465 void
13466 SetGameInfo()
13467 {
13468     /* This routine is used only for certain modes */
13469     VariantClass v = gameInfo.variant;
13470     ChessMove r = GameUnfinished;
13471     char *p = NULL;
13472
13473     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13474         r = gameInfo.result;
13475         p = gameInfo.resultDetails;
13476         gameInfo.resultDetails = NULL;
13477     }
13478     ClearGameInfo(&gameInfo);
13479     gameInfo.variant = v;
13480
13481     switch (gameMode) {
13482       case MachinePlaysWhite:
13483         gameInfo.event = StrSave( appData.pgnEventHeader );
13484         gameInfo.site = StrSave(HostName());
13485         gameInfo.date = PGNDate();
13486         gameInfo.round = StrSave("-");
13487         gameInfo.white = StrSave(first.tidy);
13488         gameInfo.black = StrSave(UserName());
13489         gameInfo.timeControl = TimeControlTagValue();
13490         break;
13491
13492       case MachinePlaysBlack:
13493         gameInfo.event = StrSave( appData.pgnEventHeader );
13494         gameInfo.site = StrSave(HostName());
13495         gameInfo.date = PGNDate();
13496         gameInfo.round = StrSave("-");
13497         gameInfo.white = StrSave(UserName());
13498         gameInfo.black = StrSave(first.tidy);
13499         gameInfo.timeControl = TimeControlTagValue();
13500         break;
13501
13502       case TwoMachinesPlay:
13503         gameInfo.event = StrSave( appData.pgnEventHeader );
13504         gameInfo.site = StrSave(HostName());
13505         gameInfo.date = PGNDate();
13506         if (matchGame > 0) {
13507             char buf[MSG_SIZ];
13508             snprintf(buf, MSG_SIZ, "%d", matchGame);
13509             gameInfo.round = StrSave(buf);
13510         } else {
13511             gameInfo.round = StrSave("-");
13512         }
13513         if (first.twoMachinesColor[0] == 'w') {
13514             gameInfo.white = StrSave(first.tidy);
13515             gameInfo.black = StrSave(second.tidy);
13516         } else {
13517             gameInfo.white = StrSave(second.tidy);
13518             gameInfo.black = StrSave(first.tidy);
13519         }
13520         gameInfo.timeControl = TimeControlTagValue();
13521         break;
13522
13523       case EditGame:
13524         gameInfo.event = StrSave("Edited game");
13525         gameInfo.site = StrSave(HostName());
13526         gameInfo.date = PGNDate();
13527         gameInfo.round = StrSave("-");
13528         gameInfo.white = StrSave("-");
13529         gameInfo.black = StrSave("-");
13530         gameInfo.result = r;
13531         gameInfo.resultDetails = p;
13532         break;
13533
13534       case EditPosition:
13535         gameInfo.event = StrSave("Edited position");
13536         gameInfo.site = StrSave(HostName());
13537         gameInfo.date = PGNDate();
13538         gameInfo.round = StrSave("-");
13539         gameInfo.white = StrSave("-");
13540         gameInfo.black = StrSave("-");
13541         break;
13542
13543       case IcsPlayingWhite:
13544       case IcsPlayingBlack:
13545       case IcsObserving:
13546       case IcsExamining:
13547         break;
13548
13549       case PlayFromGameFile:
13550         gameInfo.event = StrSave("Game from non-PGN file");
13551         gameInfo.site = StrSave(HostName());
13552         gameInfo.date = PGNDate();
13553         gameInfo.round = StrSave("-");
13554         gameInfo.white = StrSave("?");
13555         gameInfo.black = StrSave("?");
13556         break;
13557
13558       default:
13559         break;
13560     }
13561 }
13562
13563 void
13564 ReplaceComment(index, text)
13565      int index;
13566      char *text;
13567 {
13568     int len;
13569     char *p;
13570     float score;
13571
13572     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13573        pvInfoList[index-1].depth == len &&
13574        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13575        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13576     while (*text == '\n') text++;
13577     len = strlen(text);
13578     while (len > 0 && text[len - 1] == '\n') len--;
13579
13580     if (commentList[index] != NULL)
13581       free(commentList[index]);
13582
13583     if (len == 0) {
13584         commentList[index] = NULL;
13585         return;
13586     }
13587   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13588       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13589       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13590     commentList[index] = (char *) malloc(len + 2);
13591     strncpy(commentList[index], text, len);
13592     commentList[index][len] = '\n';
13593     commentList[index][len + 1] = NULLCHAR;
13594   } else {
13595     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13596     char *p;
13597     commentList[index] = (char *) malloc(len + 7);
13598     safeStrCpy(commentList[index], "{\n", 3);
13599     safeStrCpy(commentList[index]+2, text, len+1);
13600     commentList[index][len+2] = NULLCHAR;
13601     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13602     strcat(commentList[index], "\n}\n");
13603   }
13604 }
13605
13606 void
13607 CrushCRs(text)
13608      char *text;
13609 {
13610   char *p = text;
13611   char *q = text;
13612   char ch;
13613
13614   do {
13615     ch = *p++;
13616     if (ch == '\r') continue;
13617     *q++ = ch;
13618   } while (ch != '\0');
13619 }
13620
13621 void
13622 AppendComment(index, text, addBraces)
13623      int index;
13624      char *text;
13625      Boolean addBraces; // [HGM] braces: tells if we should add {}
13626 {
13627     int oldlen, len;
13628     char *old;
13629
13630 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13631     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13632
13633     CrushCRs(text);
13634     while (*text == '\n') text++;
13635     len = strlen(text);
13636     while (len > 0 && text[len - 1] == '\n') len--;
13637
13638     if (len == 0) return;
13639
13640     if (commentList[index] != NULL) {
13641         old = commentList[index];
13642         oldlen = strlen(old);
13643         while(commentList[index][oldlen-1] ==  '\n')
13644           commentList[index][--oldlen] = NULLCHAR;
13645         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13646         safeStrCpy(commentList[index], old, oldlen + len + 6);
13647         free(old);
13648         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13649         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13650           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13651           while (*text == '\n') { text++; len--; }
13652           commentList[index][--oldlen] = NULLCHAR;
13653       }
13654         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13655         else          strcat(commentList[index], "\n");
13656         strcat(commentList[index], text);
13657         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13658         else          strcat(commentList[index], "\n");
13659     } else {
13660         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13661         if(addBraces)
13662           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13663         else commentList[index][0] = NULLCHAR;
13664         strcat(commentList[index], text);
13665         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13666         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13667     }
13668 }
13669
13670 static char * FindStr( char * text, char * sub_text )
13671 {
13672     char * result = strstr( text, sub_text );
13673
13674     if( result != NULL ) {
13675         result += strlen( sub_text );
13676     }
13677
13678     return result;
13679 }
13680
13681 /* [AS] Try to extract PV info from PGN comment */
13682 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13683 char *GetInfoFromComment( int index, char * text )
13684 {
13685     char * sep = text, *p;
13686
13687     if( text != NULL && index > 0 ) {
13688         int score = 0;
13689         int depth = 0;
13690         int time = -1, sec = 0, deci;
13691         char * s_eval = FindStr( text, "[%eval " );
13692         char * s_emt = FindStr( text, "[%emt " );
13693
13694         if( s_eval != NULL || s_emt != NULL ) {
13695             /* New style */
13696             char delim;
13697
13698             if( s_eval != NULL ) {
13699                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13700                     return text;
13701                 }
13702
13703                 if( delim != ']' ) {
13704                     return text;
13705                 }
13706             }
13707
13708             if( s_emt != NULL ) {
13709             }
13710                 return text;
13711         }
13712         else {
13713             /* We expect something like: [+|-]nnn.nn/dd */
13714             int score_lo = 0;
13715
13716             if(*text != '{') return text; // [HGM] braces: must be normal comment
13717
13718             sep = strchr( text, '/' );
13719             if( sep == NULL || sep < (text+4) ) {
13720                 return text;
13721             }
13722
13723             p = text;
13724             if(p[1] == '(') { // comment starts with PV
13725                p = strchr(p, ')'); // locate end of PV
13726                if(p == NULL || sep < p+5) return text;
13727                // at this point we have something like "{(.*) +0.23/6 ..."
13728                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13729                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13730                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13731             }
13732             time = -1; sec = -1; deci = -1;
13733             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13734                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13735                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13736                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13737                 return text;
13738             }
13739
13740             if( score_lo < 0 || score_lo >= 100 ) {
13741                 return text;
13742             }
13743
13744             if(sec >= 0) time = 600*time + 10*sec; else
13745             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13746
13747             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13748
13749             /* [HGM] PV time: now locate end of PV info */
13750             while( *++sep >= '0' && *sep <= '9'); // strip depth
13751             if(time >= 0)
13752             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13753             if(sec >= 0)
13754             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13755             if(deci >= 0)
13756             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13757             while(*sep == ' ') sep++;
13758         }
13759
13760         if( depth <= 0 ) {
13761             return text;
13762         }
13763
13764         if( time < 0 ) {
13765             time = -1;
13766         }
13767
13768         pvInfoList[index-1].depth = depth;
13769         pvInfoList[index-1].score = score;
13770         pvInfoList[index-1].time  = 10*time; // centi-sec
13771         if(*sep == '}') *sep = 0; else *--sep = '{';
13772         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13773     }
13774     return sep;
13775 }
13776
13777 void
13778 SendToProgram(message, cps)
13779      char *message;
13780      ChessProgramState *cps;
13781 {
13782     int count, outCount, error;
13783     char buf[MSG_SIZ];
13784
13785     if (cps->pr == NULL) return;
13786     Attention(cps);
13787
13788     if (appData.debugMode) {
13789         TimeMark now;
13790         GetTimeMark(&now);
13791         fprintf(debugFP, "%ld >%-6s: %s",
13792                 SubtractTimeMarks(&now, &programStartTime),
13793                 cps->which, message);
13794     }
13795
13796     count = strlen(message);
13797     outCount = OutputToProcess(cps->pr, message, count, &error);
13798     if (outCount < count && !exiting
13799                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13800       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13801       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13802         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13803             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13804                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13805                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13806             } else {
13807                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13808             }
13809             gameInfo.resultDetails = StrSave(buf);
13810         }
13811         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13812     }
13813 }
13814
13815 void
13816 ReceiveFromProgram(isr, closure, message, count, error)
13817      InputSourceRef isr;
13818      VOIDSTAR closure;
13819      char *message;
13820      int count;
13821      int error;
13822 {
13823     char *end_str;
13824     char buf[MSG_SIZ];
13825     ChessProgramState *cps = (ChessProgramState *)closure;
13826
13827     if (isr != cps->isr) return; /* Killed intentionally */
13828     if (count <= 0) {
13829         if (count == 0) {
13830             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13831                     _(cps->which), cps->program);
13832         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13833                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13834                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13835                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13836                 } else {
13837                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13838                 }
13839                 gameInfo.resultDetails = StrSave(buf);
13840             }
13841             RemoveInputSource(cps->isr);
13842             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13843         } else {
13844             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13845                     _(cps->which), cps->program);
13846             RemoveInputSource(cps->isr);
13847
13848             /* [AS] Program is misbehaving badly... kill it */
13849             if( count == -2 ) {
13850                 DestroyChildProcess( cps->pr, 9 );
13851                 cps->pr = NoProc;
13852             }
13853
13854             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13855         }
13856         return;
13857     }
13858
13859     if ((end_str = strchr(message, '\r')) != NULL)
13860       *end_str = NULLCHAR;
13861     if ((end_str = strchr(message, '\n')) != NULL)
13862       *end_str = NULLCHAR;
13863
13864     if (appData.debugMode) {
13865         TimeMark now; int print = 1;
13866         char *quote = ""; char c; int i;
13867
13868         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13869                 char start = message[0];
13870                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13871                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13872                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13873                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13874                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13875                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13876                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13877                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13878                    sscanf(message, "hint: %c", &c)!=1 && 
13879                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13880                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13881                     print = (appData.engineComments >= 2);
13882                 }
13883                 message[0] = start; // restore original message
13884         }
13885         if(print) {
13886                 GetTimeMark(&now);
13887                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13888                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13889                         quote,
13890                         message);
13891         }
13892     }
13893
13894     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13895     if (appData.icsEngineAnalyze) {
13896         if (strstr(message, "whisper") != NULL ||
13897              strstr(message, "kibitz") != NULL ||
13898             strstr(message, "tellics") != NULL) return;
13899     }
13900
13901     HandleMachineMove(message, cps);
13902 }
13903
13904
13905 void
13906 SendTimeControl(cps, mps, tc, inc, sd, st)
13907      ChessProgramState *cps;
13908      int mps, inc, sd, st;
13909      long tc;
13910 {
13911     char buf[MSG_SIZ];
13912     int seconds;
13913
13914     if( timeControl_2 > 0 ) {
13915         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13916             tc = timeControl_2;
13917         }
13918     }
13919     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13920     inc /= cps->timeOdds;
13921     st  /= cps->timeOdds;
13922
13923     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13924
13925     if (st > 0) {
13926       /* Set exact time per move, normally using st command */
13927       if (cps->stKludge) {
13928         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13929         seconds = st % 60;
13930         if (seconds == 0) {
13931           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13932         } else {
13933           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13934         }
13935       } else {
13936         snprintf(buf, MSG_SIZ, "st %d\n", st);
13937       }
13938     } else {
13939       /* Set conventional or incremental time control, using level command */
13940       if (seconds == 0) {
13941         /* Note old gnuchess bug -- minutes:seconds used to not work.
13942            Fixed in later versions, but still avoid :seconds
13943            when seconds is 0. */
13944         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13945       } else {
13946         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13947                  seconds, inc/1000.);
13948       }
13949     }
13950     SendToProgram(buf, cps);
13951
13952     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13953     /* Orthogonally, limit search to given depth */
13954     if (sd > 0) {
13955       if (cps->sdKludge) {
13956         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13957       } else {
13958         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13959       }
13960       SendToProgram(buf, cps);
13961     }
13962
13963     if(cps->nps >= 0) { /* [HGM] nps */
13964         if(cps->supportsNPS == FALSE)
13965           cps->nps = -1; // don't use if engine explicitly says not supported!
13966         else {
13967           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13968           SendToProgram(buf, cps);
13969         }
13970     }
13971 }
13972
13973 ChessProgramState *WhitePlayer()
13974 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13975 {
13976     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13977        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13978         return &second;
13979     return &first;
13980 }
13981
13982 void
13983 SendTimeRemaining(cps, machineWhite)
13984      ChessProgramState *cps;
13985      int /*boolean*/ machineWhite;
13986 {
13987     char message[MSG_SIZ];
13988     long time, otime;
13989
13990     /* Note: this routine must be called when the clocks are stopped
13991        or when they have *just* been set or switched; otherwise
13992        it will be off by the time since the current tick started.
13993     */
13994     if (machineWhite) {
13995         time = whiteTimeRemaining / 10;
13996         otime = blackTimeRemaining / 10;
13997     } else {
13998         time = blackTimeRemaining / 10;
13999         otime = whiteTimeRemaining / 10;
14000     }
14001     /* [HGM] translate opponent's time by time-odds factor */
14002     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14003     if (appData.debugMode) {
14004         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14005     }
14006
14007     if (time <= 0) time = 1;
14008     if (otime <= 0) otime = 1;
14009
14010     snprintf(message, MSG_SIZ, "time %ld\n", time);
14011     SendToProgram(message, cps);
14012
14013     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14014     SendToProgram(message, cps);
14015 }
14016
14017 int
14018 BoolFeature(p, name, loc, cps)
14019      char **p;
14020      char *name;
14021      int *loc;
14022      ChessProgramState *cps;
14023 {
14024   char buf[MSG_SIZ];
14025   int len = strlen(name);
14026   int val;
14027
14028   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14029     (*p) += len + 1;
14030     sscanf(*p, "%d", &val);
14031     *loc = (val != 0);
14032     while (**p && **p != ' ')
14033       (*p)++;
14034     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14035     SendToProgram(buf, cps);
14036     return TRUE;
14037   }
14038   return FALSE;
14039 }
14040
14041 int
14042 IntFeature(p, name, loc, cps)
14043      char **p;
14044      char *name;
14045      int *loc;
14046      ChessProgramState *cps;
14047 {
14048   char buf[MSG_SIZ];
14049   int len = strlen(name);
14050   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14051     (*p) += len + 1;
14052     sscanf(*p, "%d", loc);
14053     while (**p && **p != ' ') (*p)++;
14054     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14055     SendToProgram(buf, cps);
14056     return TRUE;
14057   }
14058   return FALSE;
14059 }
14060
14061 int
14062 StringFeature(p, name, loc, cps)
14063      char **p;
14064      char *name;
14065      char loc[];
14066      ChessProgramState *cps;
14067 {
14068   char buf[MSG_SIZ];
14069   int len = strlen(name);
14070   if (strncmp((*p), name, len) == 0
14071       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14072     (*p) += len + 2;
14073     sscanf(*p, "%[^\"]", loc);
14074     while (**p && **p != '\"') (*p)++;
14075     if (**p == '\"') (*p)++;
14076     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14077     SendToProgram(buf, cps);
14078     return TRUE;
14079   }
14080   return FALSE;
14081 }
14082
14083 int
14084 ParseOption(Option *opt, ChessProgramState *cps)
14085 // [HGM] options: process the string that defines an engine option, and determine
14086 // name, type, default value, and allowed value range
14087 {
14088         char *p, *q, buf[MSG_SIZ];
14089         int n, min = (-1)<<31, max = 1<<31, def;
14090
14091         if(p = strstr(opt->name, " -spin ")) {
14092             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14093             if(max < min) max = min; // enforce consistency
14094             if(def < min) def = min;
14095             if(def > max) def = max;
14096             opt->value = def;
14097             opt->min = min;
14098             opt->max = max;
14099             opt->type = Spin;
14100         } else if((p = strstr(opt->name, " -slider "))) {
14101             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14102             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14103             if(max < min) max = min; // enforce consistency
14104             if(def < min) def = min;
14105             if(def > max) def = max;
14106             opt->value = def;
14107             opt->min = min;
14108             opt->max = max;
14109             opt->type = Spin; // Slider;
14110         } else if((p = strstr(opt->name, " -string "))) {
14111             opt->textValue = p+9;
14112             opt->type = TextBox;
14113         } else if((p = strstr(opt->name, " -file "))) {
14114             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14115             opt->textValue = p+7;
14116             opt->type = FileName; // FileName;
14117         } else if((p = strstr(opt->name, " -path "))) {
14118             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14119             opt->textValue = p+7;
14120             opt->type = PathName; // PathName;
14121         } else if(p = strstr(opt->name, " -check ")) {
14122             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14123             opt->value = (def != 0);
14124             opt->type = CheckBox;
14125         } else if(p = strstr(opt->name, " -combo ")) {
14126             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14127             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14128             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14129             opt->value = n = 0;
14130             while(q = StrStr(q, " /// ")) {
14131                 n++; *q = 0;    // count choices, and null-terminate each of them
14132                 q += 5;
14133                 if(*q == '*') { // remember default, which is marked with * prefix
14134                     q++;
14135                     opt->value = n;
14136                 }
14137                 cps->comboList[cps->comboCnt++] = q;
14138             }
14139             cps->comboList[cps->comboCnt++] = NULL;
14140             opt->max = n + 1;
14141             opt->type = ComboBox;
14142         } else if(p = strstr(opt->name, " -button")) {
14143             opt->type = Button;
14144         } else if(p = strstr(opt->name, " -save")) {
14145             opt->type = SaveButton;
14146         } else return FALSE;
14147         *p = 0; // terminate option name
14148         // now look if the command-line options define a setting for this engine option.
14149         if(cps->optionSettings && cps->optionSettings[0])
14150             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14151         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14152           snprintf(buf, MSG_SIZ, "option %s", p);
14153                 if(p = strstr(buf, ",")) *p = 0;
14154                 if(q = strchr(buf, '=')) switch(opt->type) {
14155                     case ComboBox:
14156                         for(n=0; n<opt->max; n++)
14157                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14158                         break;
14159                     case TextBox:
14160                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14161                         break;
14162                     case Spin:
14163                     case CheckBox:
14164                         opt->value = atoi(q+1);
14165                     default:
14166                         break;
14167                 }
14168                 strcat(buf, "\n");
14169                 SendToProgram(buf, cps);
14170         }
14171         return TRUE;
14172 }
14173
14174 void
14175 FeatureDone(cps, val)
14176      ChessProgramState* cps;
14177      int val;
14178 {
14179   DelayedEventCallback cb = GetDelayedEvent();
14180   if ((cb == InitBackEnd3 && cps == &first) ||
14181       (cb == SettingsMenuIfReady && cps == &second) ||
14182       (cb == LoadEngine) ||
14183       (cb == TwoMachinesEventIfReady && cps == &second)) {
14184     CancelDelayedEvent();
14185     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14186   }
14187   cps->initDone = val;
14188 }
14189
14190 /* Parse feature command from engine */
14191 void
14192 ParseFeatures(args, cps)
14193      char* args;
14194      ChessProgramState *cps;
14195 {
14196   char *p = args;
14197   char *q;
14198   int val;
14199   char buf[MSG_SIZ];
14200
14201   for (;;) {
14202     while (*p == ' ') p++;
14203     if (*p == NULLCHAR) return;
14204
14205     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14206     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14207     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14208     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14209     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14210     if (BoolFeature(&p, "reuse", &val, cps)) {
14211       /* Engine can disable reuse, but can't enable it if user said no */
14212       if (!val) cps->reuse = FALSE;
14213       continue;
14214     }
14215     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14216     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14217       if (gameMode == TwoMachinesPlay) {
14218         DisplayTwoMachinesTitle();
14219       } else {
14220         DisplayTitle("");
14221       }
14222       continue;
14223     }
14224     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14225     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14226     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14227     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14228     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14229     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14230     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14231     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14232     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14233     if (IntFeature(&p, "done", &val, cps)) {
14234       FeatureDone(cps, val);
14235       continue;
14236     }
14237     /* Added by Tord: */
14238     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14239     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14240     /* End of additions by Tord */
14241
14242     /* [HGM] added features: */
14243     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14244     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14245     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14246     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14247     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14248     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14249     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14250         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14251           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14252             SendToProgram(buf, cps);
14253             continue;
14254         }
14255         if(cps->nrOptions >= MAX_OPTIONS) {
14256             cps->nrOptions--;
14257             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14258             DisplayError(buf, 0);
14259         }
14260         continue;
14261     }
14262     /* End of additions by HGM */
14263
14264     /* unknown feature: complain and skip */
14265     q = p;
14266     while (*q && *q != '=') q++;
14267     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14268     SendToProgram(buf, cps);
14269     p = q;
14270     if (*p == '=') {
14271       p++;
14272       if (*p == '\"') {
14273         p++;
14274         while (*p && *p != '\"') p++;
14275         if (*p == '\"') p++;
14276       } else {
14277         while (*p && *p != ' ') p++;
14278       }
14279     }
14280   }
14281
14282 }
14283
14284 void
14285 PeriodicUpdatesEvent(newState)
14286      int newState;
14287 {
14288     if (newState == appData.periodicUpdates)
14289       return;
14290
14291     appData.periodicUpdates=newState;
14292
14293     /* Display type changes, so update it now */
14294 //    DisplayAnalysis();
14295
14296     /* Get the ball rolling again... */
14297     if (newState) {
14298         AnalysisPeriodicEvent(1);
14299         StartAnalysisClock();
14300     }
14301 }
14302
14303 void
14304 PonderNextMoveEvent(newState)
14305      int newState;
14306 {
14307     if (newState == appData.ponderNextMove) return;
14308     if (gameMode == EditPosition) EditPositionDone(TRUE);
14309     if (newState) {
14310         SendToProgram("hard\n", &first);
14311         if (gameMode == TwoMachinesPlay) {
14312             SendToProgram("hard\n", &second);
14313         }
14314     } else {
14315         SendToProgram("easy\n", &first);
14316         thinkOutput[0] = NULLCHAR;
14317         if (gameMode == TwoMachinesPlay) {
14318             SendToProgram("easy\n", &second);
14319         }
14320     }
14321     appData.ponderNextMove = newState;
14322 }
14323
14324 void
14325 NewSettingEvent(option, feature, command, value)
14326      char *command;
14327      int option, value, *feature;
14328 {
14329     char buf[MSG_SIZ];
14330
14331     if (gameMode == EditPosition) EditPositionDone(TRUE);
14332     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14333     if(feature == NULL || *feature) SendToProgram(buf, &first);
14334     if (gameMode == TwoMachinesPlay) {
14335         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14336     }
14337 }
14338
14339 void
14340 ShowThinkingEvent()
14341 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14342 {
14343     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14344     int newState = appData.showThinking
14345         // [HGM] thinking: other features now need thinking output as well
14346         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14347
14348     if (oldState == newState) return;
14349     oldState = newState;
14350     if (gameMode == EditPosition) EditPositionDone(TRUE);
14351     if (oldState) {
14352         SendToProgram("post\n", &first);
14353         if (gameMode == TwoMachinesPlay) {
14354             SendToProgram("post\n", &second);
14355         }
14356     } else {
14357         SendToProgram("nopost\n", &first);
14358         thinkOutput[0] = NULLCHAR;
14359         if (gameMode == TwoMachinesPlay) {
14360             SendToProgram("nopost\n", &second);
14361         }
14362     }
14363 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14364 }
14365
14366 void
14367 AskQuestionEvent(title, question, replyPrefix, which)
14368      char *title; char *question; char *replyPrefix; char *which;
14369 {
14370   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14371   if (pr == NoProc) return;
14372   AskQuestion(title, question, replyPrefix, pr);
14373 }
14374
14375 void
14376 TypeInEvent(char firstChar)
14377 {
14378     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14379         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14380         gameMode == AnalyzeMode || gameMode == EditGame || \r
14381         gameMode == EditPosition || gameMode == IcsExamining ||\r
14382         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14383         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14384                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14385                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14386         gameMode == Training) PopUpMoveDialog(firstChar);
14387 }
14388
14389 void
14390 TypeInDoneEvent(char *move)
14391 {
14392         Board board;
14393         int n, fromX, fromY, toX, toY;
14394         char promoChar;
14395         ChessMove moveType;\r
14396
14397         // [HGM] FENedit\r
14398         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14399                 EditPositionPasteFEN(move);\r
14400                 return;\r
14401         }\r
14402         // [HGM] movenum: allow move number to be typed in any mode\r
14403         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14404           ToNrEvent(2*n-1);\r
14405           return;\r
14406         }\r
14407
14408       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14409         gameMode != Training) {\r
14410         DisplayMoveError(_("Displayed move is not current"));\r
14411       } else {\r
14412         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14413           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14414         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14415         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14416           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14417           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14418         } else {\r
14419           DisplayMoveError(_("Could not parse move"));\r
14420         }
14421       }\r
14422 }\r
14423
14424 void
14425 DisplayMove(moveNumber)
14426      int moveNumber;
14427 {
14428     char message[MSG_SIZ];
14429     char res[MSG_SIZ];
14430     char cpThinkOutput[MSG_SIZ];
14431
14432     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14433
14434     if (moveNumber == forwardMostMove - 1 ||
14435         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14436
14437         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14438
14439         if (strchr(cpThinkOutput, '\n')) {
14440             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14441         }
14442     } else {
14443         *cpThinkOutput = NULLCHAR;
14444     }
14445
14446     /* [AS] Hide thinking from human user */
14447     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14448         *cpThinkOutput = NULLCHAR;
14449         if( thinkOutput[0] != NULLCHAR ) {
14450             int i;
14451
14452             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14453                 cpThinkOutput[i] = '.';
14454             }
14455             cpThinkOutput[i] = NULLCHAR;
14456             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14457         }
14458     }
14459
14460     if (moveNumber == forwardMostMove - 1 &&
14461         gameInfo.resultDetails != NULL) {
14462         if (gameInfo.resultDetails[0] == NULLCHAR) {
14463           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14464         } else {
14465           snprintf(res, MSG_SIZ, " {%s} %s",
14466                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14467         }
14468     } else {
14469         res[0] = NULLCHAR;
14470     }
14471
14472     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14473         DisplayMessage(res, cpThinkOutput);
14474     } else {
14475       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14476                 WhiteOnMove(moveNumber) ? " " : ".. ",
14477                 parseList[moveNumber], res);
14478         DisplayMessage(message, cpThinkOutput);
14479     }
14480 }
14481
14482 void
14483 DisplayComment(moveNumber, text)
14484      int moveNumber;
14485      char *text;
14486 {
14487     char title[MSG_SIZ];
14488     char buf[8000]; // comment can be long!
14489     int score, depth;
14490
14491     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14492       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14493     } else {
14494       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14495               WhiteOnMove(moveNumber) ? " " : ".. ",
14496               parseList[moveNumber]);
14497     }
14498     // [HGM] PV info: display PV info together with (or as) comment
14499     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14500       if(text == NULL) text = "";
14501       score = pvInfoList[moveNumber].score;
14502       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14503               depth, (pvInfoList[moveNumber].time+50)/100, text);
14504       text = buf;
14505     }
14506     if (text != NULL && (appData.autoDisplayComment || commentUp))
14507         CommentPopUp(title, text);
14508 }
14509
14510 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14511  * might be busy thinking or pondering.  It can be omitted if your
14512  * gnuchess is configured to stop thinking immediately on any user
14513  * input.  However, that gnuchess feature depends on the FIONREAD
14514  * ioctl, which does not work properly on some flavors of Unix.
14515  */
14516 void
14517 Attention(cps)
14518      ChessProgramState *cps;
14519 {
14520 #if ATTENTION
14521     if (!cps->useSigint) return;
14522     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14523     switch (gameMode) {
14524       case MachinePlaysWhite:
14525       case MachinePlaysBlack:
14526       case TwoMachinesPlay:
14527       case IcsPlayingWhite:
14528       case IcsPlayingBlack:
14529       case AnalyzeMode:
14530       case AnalyzeFile:
14531         /* Skip if we know it isn't thinking */
14532         if (!cps->maybeThinking) return;
14533         if (appData.debugMode)
14534           fprintf(debugFP, "Interrupting %s\n", cps->which);
14535         InterruptChildProcess(cps->pr);
14536         cps->maybeThinking = FALSE;
14537         break;
14538       default:
14539         break;
14540     }
14541 #endif /*ATTENTION*/
14542 }
14543
14544 int
14545 CheckFlags()
14546 {
14547     if (whiteTimeRemaining <= 0) {
14548         if (!whiteFlag) {
14549             whiteFlag = TRUE;
14550             if (appData.icsActive) {
14551                 if (appData.autoCallFlag &&
14552                     gameMode == IcsPlayingBlack && !blackFlag) {
14553                   SendToICS(ics_prefix);
14554                   SendToICS("flag\n");
14555                 }
14556             } else {
14557                 if (blackFlag) {
14558                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14559                 } else {
14560                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14561                     if (appData.autoCallFlag) {
14562                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14563                         return TRUE;
14564                     }
14565                 }
14566             }
14567         }
14568     }
14569     if (blackTimeRemaining <= 0) {
14570         if (!blackFlag) {
14571             blackFlag = TRUE;
14572             if (appData.icsActive) {
14573                 if (appData.autoCallFlag &&
14574                     gameMode == IcsPlayingWhite && !whiteFlag) {
14575                   SendToICS(ics_prefix);
14576                   SendToICS("flag\n");
14577                 }
14578             } else {
14579                 if (whiteFlag) {
14580                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14581                 } else {
14582                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14583                     if (appData.autoCallFlag) {
14584                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14585                         return TRUE;
14586                     }
14587                 }
14588             }
14589         }
14590     }
14591     return FALSE;
14592 }
14593
14594 void
14595 CheckTimeControl()
14596 {
14597     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14598         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14599
14600     /*
14601      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14602      */
14603     if ( !WhiteOnMove(forwardMostMove) ) {
14604         /* White made time control */
14605         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14606         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14607         /* [HGM] time odds: correct new time quota for time odds! */
14608                                             / WhitePlayer()->timeOdds;
14609         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14610     } else {
14611         lastBlack -= blackTimeRemaining;
14612         /* Black made time control */
14613         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14614                                             / WhitePlayer()->other->timeOdds;
14615         lastWhite = whiteTimeRemaining;
14616     }
14617 }
14618
14619 void
14620 DisplayBothClocks()
14621 {
14622     int wom = gameMode == EditPosition ?
14623       !blackPlaysFirst : WhiteOnMove(currentMove);
14624     DisplayWhiteClock(whiteTimeRemaining, wom);
14625     DisplayBlackClock(blackTimeRemaining, !wom);
14626 }
14627
14628
14629 /* Timekeeping seems to be a portability nightmare.  I think everyone
14630    has ftime(), but I'm really not sure, so I'm including some ifdefs
14631    to use other calls if you don't.  Clocks will be less accurate if
14632    you have neither ftime nor gettimeofday.
14633 */
14634
14635 /* VS 2008 requires the #include outside of the function */
14636 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14637 #include <sys/timeb.h>
14638 #endif
14639
14640 /* Get the current time as a TimeMark */
14641 void
14642 GetTimeMark(tm)
14643      TimeMark *tm;
14644 {
14645 #if HAVE_GETTIMEOFDAY
14646
14647     struct timeval timeVal;
14648     struct timezone timeZone;
14649
14650     gettimeofday(&timeVal, &timeZone);
14651     tm->sec = (long) timeVal.tv_sec;
14652     tm->ms = (int) (timeVal.tv_usec / 1000L);
14653
14654 #else /*!HAVE_GETTIMEOFDAY*/
14655 #if HAVE_FTIME
14656
14657 // include <sys/timeb.h> / moved to just above start of function
14658     struct timeb timeB;
14659
14660     ftime(&timeB);
14661     tm->sec = (long) timeB.time;
14662     tm->ms = (int) timeB.millitm;
14663
14664 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14665     tm->sec = (long) time(NULL);
14666     tm->ms = 0;
14667 #endif
14668 #endif
14669 }
14670
14671 /* Return the difference in milliseconds between two
14672    time marks.  We assume the difference will fit in a long!
14673 */
14674 long
14675 SubtractTimeMarks(tm2, tm1)
14676      TimeMark *tm2, *tm1;
14677 {
14678     return 1000L*(tm2->sec - tm1->sec) +
14679            (long) (tm2->ms - tm1->ms);
14680 }
14681
14682
14683 /*
14684  * Code to manage the game clocks.
14685  *
14686  * In tournament play, black starts the clock and then white makes a move.
14687  * We give the human user a slight advantage if he is playing white---the
14688  * clocks don't run until he makes his first move, so it takes zero time.
14689  * Also, we don't account for network lag, so we could get out of sync
14690  * with GNU Chess's clock -- but then, referees are always right.
14691  */
14692
14693 static TimeMark tickStartTM;
14694 static long intendedTickLength;
14695
14696 long
14697 NextTickLength(timeRemaining)
14698      long timeRemaining;
14699 {
14700     long nominalTickLength, nextTickLength;
14701
14702     if (timeRemaining > 0L && timeRemaining <= 10000L)
14703       nominalTickLength = 100L;
14704     else
14705       nominalTickLength = 1000L;
14706     nextTickLength = timeRemaining % nominalTickLength;
14707     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14708
14709     return nextTickLength;
14710 }
14711
14712 /* Adjust clock one minute up or down */
14713 void
14714 AdjustClock(Boolean which, int dir)
14715 {
14716     if(which) blackTimeRemaining += 60000*dir;
14717     else      whiteTimeRemaining += 60000*dir;
14718     DisplayBothClocks();
14719 }
14720
14721 /* Stop clocks and reset to a fresh time control */
14722 void
14723 ResetClocks()
14724 {
14725     (void) StopClockTimer();
14726     if (appData.icsActive) {
14727         whiteTimeRemaining = blackTimeRemaining = 0;
14728     } else if (searchTime) {
14729         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14730         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14731     } else { /* [HGM] correct new time quote for time odds */
14732         whiteTC = blackTC = fullTimeControlString;
14733         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14734         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14735     }
14736     if (whiteFlag || blackFlag) {
14737         DisplayTitle("");
14738         whiteFlag = blackFlag = FALSE;
14739     }
14740     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14741     DisplayBothClocks();
14742 }
14743
14744 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14745
14746 /* Decrement running clock by amount of time that has passed */
14747 void
14748 DecrementClocks()
14749 {
14750     long timeRemaining;
14751     long lastTickLength, fudge;
14752     TimeMark now;
14753
14754     if (!appData.clockMode) return;
14755     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14756
14757     GetTimeMark(&now);
14758
14759     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14760
14761     /* Fudge if we woke up a little too soon */
14762     fudge = intendedTickLength - lastTickLength;
14763     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14764
14765     if (WhiteOnMove(forwardMostMove)) {
14766         if(whiteNPS >= 0) lastTickLength = 0;
14767         timeRemaining = whiteTimeRemaining -= lastTickLength;
14768         if(timeRemaining < 0 && !appData.icsActive) {
14769             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14770             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14771                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14772                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14773             }
14774         }
14775         DisplayWhiteClock(whiteTimeRemaining - fudge,
14776                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14777     } else {
14778         if(blackNPS >= 0) lastTickLength = 0;
14779         timeRemaining = blackTimeRemaining -= lastTickLength;
14780         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14781             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14782             if(suddenDeath) {
14783                 blackStartMove = forwardMostMove;
14784                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14785             }
14786         }
14787         DisplayBlackClock(blackTimeRemaining - fudge,
14788                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14789     }
14790     if (CheckFlags()) return;
14791
14792     tickStartTM = now;
14793     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14794     StartClockTimer(intendedTickLength);
14795
14796     /* if the time remaining has fallen below the alarm threshold, sound the
14797      * alarm. if the alarm has sounded and (due to a takeback or time control
14798      * with increment) the time remaining has increased to a level above the
14799      * threshold, reset the alarm so it can sound again.
14800      */
14801
14802     if (appData.icsActive && appData.icsAlarm) {
14803
14804         /* make sure we are dealing with the user's clock */
14805         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14806                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14807            )) return;
14808
14809         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14810             alarmSounded = FALSE;
14811         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14812             PlayAlarmSound();
14813             alarmSounded = TRUE;
14814         }
14815     }
14816 }
14817
14818
14819 /* A player has just moved, so stop the previously running
14820    clock and (if in clock mode) start the other one.
14821    We redisplay both clocks in case we're in ICS mode, because
14822    ICS gives us an update to both clocks after every move.
14823    Note that this routine is called *after* forwardMostMove
14824    is updated, so the last fractional tick must be subtracted
14825    from the color that is *not* on move now.
14826 */
14827 void
14828 SwitchClocks(int newMoveNr)
14829 {
14830     long lastTickLength;
14831     TimeMark now;
14832     int flagged = FALSE;
14833
14834     GetTimeMark(&now);
14835
14836     if (StopClockTimer() && appData.clockMode) {
14837         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14838         if (!WhiteOnMove(forwardMostMove)) {
14839             if(blackNPS >= 0) lastTickLength = 0;
14840             blackTimeRemaining -= lastTickLength;
14841            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14842 //         if(pvInfoList[forwardMostMove].time == -1)
14843                  pvInfoList[forwardMostMove].time =               // use GUI time
14844                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14845         } else {
14846            if(whiteNPS >= 0) lastTickLength = 0;
14847            whiteTimeRemaining -= lastTickLength;
14848            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14849 //         if(pvInfoList[forwardMostMove].time == -1)
14850                  pvInfoList[forwardMostMove].time =
14851                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14852         }
14853         flagged = CheckFlags();
14854     }
14855     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14856     CheckTimeControl();
14857
14858     if (flagged || !appData.clockMode) return;
14859
14860     switch (gameMode) {
14861       case MachinePlaysBlack:
14862       case MachinePlaysWhite:
14863       case BeginningOfGame:
14864         if (pausing) return;
14865         break;
14866
14867       case EditGame:
14868       case PlayFromGameFile:
14869       case IcsExamining:
14870         return;
14871
14872       default:
14873         break;
14874     }
14875
14876     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14877         if(WhiteOnMove(forwardMostMove))
14878              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14879         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14880     }
14881
14882     tickStartTM = now;
14883     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14884       whiteTimeRemaining : blackTimeRemaining);
14885     StartClockTimer(intendedTickLength);
14886 }
14887
14888
14889 /* Stop both clocks */
14890 void
14891 StopClocks()
14892 {
14893     long lastTickLength;
14894     TimeMark now;
14895
14896     if (!StopClockTimer()) return;
14897     if (!appData.clockMode) return;
14898
14899     GetTimeMark(&now);
14900
14901     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14902     if (WhiteOnMove(forwardMostMove)) {
14903         if(whiteNPS >= 0) lastTickLength = 0;
14904         whiteTimeRemaining -= lastTickLength;
14905         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14906     } else {
14907         if(blackNPS >= 0) lastTickLength = 0;
14908         blackTimeRemaining -= lastTickLength;
14909         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14910     }
14911     CheckFlags();
14912 }
14913
14914 /* Start clock of player on move.  Time may have been reset, so
14915    if clock is already running, stop and restart it. */
14916 void
14917 StartClocks()
14918 {
14919     (void) StopClockTimer(); /* in case it was running already */
14920     DisplayBothClocks();
14921     if (CheckFlags()) return;
14922
14923     if (!appData.clockMode) return;
14924     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14925
14926     GetTimeMark(&tickStartTM);
14927     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14928       whiteTimeRemaining : blackTimeRemaining);
14929
14930    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14931     whiteNPS = blackNPS = -1;
14932     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14933        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14934         whiteNPS = first.nps;
14935     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14936        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14937         blackNPS = first.nps;
14938     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14939         whiteNPS = second.nps;
14940     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14941         blackNPS = second.nps;
14942     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14943
14944     StartClockTimer(intendedTickLength);
14945 }
14946
14947 char *
14948 TimeString(ms)
14949      long ms;
14950 {
14951     long second, minute, hour, day;
14952     char *sign = "";
14953     static char buf[32];
14954
14955     if (ms > 0 && ms <= 9900) {
14956       /* convert milliseconds to tenths, rounding up */
14957       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14958
14959       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14960       return buf;
14961     }
14962
14963     /* convert milliseconds to seconds, rounding up */
14964     /* use floating point to avoid strangeness of integer division
14965        with negative dividends on many machines */
14966     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14967
14968     if (second < 0) {
14969         sign = "-";
14970         second = -second;
14971     }
14972
14973     day = second / (60 * 60 * 24);
14974     second = second % (60 * 60 * 24);
14975     hour = second / (60 * 60);
14976     second = second % (60 * 60);
14977     minute = second / 60;
14978     second = second % 60;
14979
14980     if (day > 0)
14981       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14982               sign, day, hour, minute, second);
14983     else if (hour > 0)
14984       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14985     else
14986       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14987
14988     return buf;
14989 }
14990
14991
14992 /*
14993  * This is necessary because some C libraries aren't ANSI C compliant yet.
14994  */
14995 char *
14996 StrStr(string, match)
14997      char *string, *match;
14998 {
14999     int i, length;
15000
15001     length = strlen(match);
15002
15003     for (i = strlen(string) - length; i >= 0; i--, string++)
15004       if (!strncmp(match, string, length))
15005         return string;
15006
15007     return NULL;
15008 }
15009
15010 char *
15011 StrCaseStr(string, match)
15012      char *string, *match;
15013 {
15014     int i, j, length;
15015
15016     length = strlen(match);
15017
15018     for (i = strlen(string) - length; i >= 0; i--, string++) {
15019         for (j = 0; j < length; j++) {
15020             if (ToLower(match[j]) != ToLower(string[j]))
15021               break;
15022         }
15023         if (j == length) return string;
15024     }
15025
15026     return NULL;
15027 }
15028
15029 #ifndef _amigados
15030 int
15031 StrCaseCmp(s1, s2)
15032      char *s1, *s2;
15033 {
15034     char c1, c2;
15035
15036     for (;;) {
15037         c1 = ToLower(*s1++);
15038         c2 = ToLower(*s2++);
15039         if (c1 > c2) return 1;
15040         if (c1 < c2) return -1;
15041         if (c1 == NULLCHAR) return 0;
15042     }
15043 }
15044
15045
15046 int
15047 ToLower(c)
15048      int c;
15049 {
15050     return isupper(c) ? tolower(c) : c;
15051 }
15052
15053
15054 int
15055 ToUpper(c)
15056      int c;
15057 {
15058     return islower(c) ? toupper(c) : c;
15059 }
15060 #endif /* !_amigados    */
15061
15062 char *
15063 StrSave(s)
15064      char *s;
15065 {
15066   char *ret;
15067
15068   if ((ret = (char *) malloc(strlen(s) + 1)))
15069     {
15070       safeStrCpy(ret, s, strlen(s)+1);
15071     }
15072   return ret;
15073 }
15074
15075 char *
15076 StrSavePtr(s, savePtr)
15077      char *s, **savePtr;
15078 {
15079     if (*savePtr) {
15080         free(*savePtr);
15081     }
15082     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15083       safeStrCpy(*savePtr, s, strlen(s)+1);
15084     }
15085     return(*savePtr);
15086 }
15087
15088 char *
15089 PGNDate()
15090 {
15091     time_t clock;
15092     struct tm *tm;
15093     char buf[MSG_SIZ];
15094
15095     clock = time((time_t *)NULL);
15096     tm = localtime(&clock);
15097     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15098             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15099     return StrSave(buf);
15100 }
15101
15102
15103 char *
15104 PositionToFEN(move, overrideCastling)
15105      int move;
15106      char *overrideCastling;
15107 {
15108     int i, j, fromX, fromY, toX, toY;
15109     int whiteToPlay;
15110     char buf[128];
15111     char *p, *q;
15112     int emptycount;
15113     ChessSquare piece;
15114
15115     whiteToPlay = (gameMode == EditPosition) ?
15116       !blackPlaysFirst : (move % 2 == 0);
15117     p = buf;
15118
15119     /* Piece placement data */
15120     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15121         emptycount = 0;
15122         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15123             if (boards[move][i][j] == EmptySquare) {
15124                 emptycount++;
15125             } else { ChessSquare piece = boards[move][i][j];
15126                 if (emptycount > 0) {
15127                     if(emptycount<10) /* [HGM] can be >= 10 */
15128                         *p++ = '0' + emptycount;
15129                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15130                     emptycount = 0;
15131                 }
15132                 if(PieceToChar(piece) == '+') {
15133                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15134                     *p++ = '+';
15135                     piece = (ChessSquare)(DEMOTED piece);
15136                 }
15137                 *p++ = PieceToChar(piece);
15138                 if(p[-1] == '~') {
15139                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15140                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15141                     *p++ = '~';
15142                 }
15143             }
15144         }
15145         if (emptycount > 0) {
15146             if(emptycount<10) /* [HGM] can be >= 10 */
15147                 *p++ = '0' + emptycount;
15148             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15149             emptycount = 0;
15150         }
15151         *p++ = '/';
15152     }
15153     *(p - 1) = ' ';
15154
15155     /* [HGM] print Crazyhouse or Shogi holdings */
15156     if( gameInfo.holdingsWidth ) {
15157         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15158         q = p;
15159         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15160             piece = boards[move][i][BOARD_WIDTH-1];
15161             if( piece != EmptySquare )
15162               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15163                   *p++ = PieceToChar(piece);
15164         }
15165         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15166             piece = boards[move][BOARD_HEIGHT-i-1][0];
15167             if( piece != EmptySquare )
15168               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15169                   *p++ = PieceToChar(piece);
15170         }
15171
15172         if( q == p ) *p++ = '-';
15173         *p++ = ']';
15174         *p++ = ' ';
15175     }
15176
15177     /* Active color */
15178     *p++ = whiteToPlay ? 'w' : 'b';
15179     *p++ = ' ';
15180
15181   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15182     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15183   } else {
15184   if(nrCastlingRights) {
15185      q = p;
15186      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15187        /* [HGM] write directly from rights */
15188            if(boards[move][CASTLING][2] != NoRights &&
15189               boards[move][CASTLING][0] != NoRights   )
15190                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15191            if(boards[move][CASTLING][2] != NoRights &&
15192               boards[move][CASTLING][1] != NoRights   )
15193                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15194            if(boards[move][CASTLING][5] != NoRights &&
15195               boards[move][CASTLING][3] != NoRights   )
15196                 *p++ = boards[move][CASTLING][3] + AAA;
15197            if(boards[move][CASTLING][5] != NoRights &&
15198               boards[move][CASTLING][4] != NoRights   )
15199                 *p++ = boards[move][CASTLING][4] + AAA;
15200      } else {
15201
15202         /* [HGM] write true castling rights */
15203         if( nrCastlingRights == 6 ) {
15204             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15205                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15206             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15207                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15208             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15209                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15210             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15211                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15212         }
15213      }
15214      if (q == p) *p++ = '-'; /* No castling rights */
15215      *p++ = ' ';
15216   }
15217
15218   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15219      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15220     /* En passant target square */
15221     if (move > backwardMostMove) {
15222         fromX = moveList[move - 1][0] - AAA;
15223         fromY = moveList[move - 1][1] - ONE;
15224         toX = moveList[move - 1][2] - AAA;
15225         toY = moveList[move - 1][3] - ONE;
15226         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15227             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15228             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15229             fromX == toX) {
15230             /* 2-square pawn move just happened */
15231             *p++ = toX + AAA;
15232             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15233         } else {
15234             *p++ = '-';
15235         }
15236     } else if(move == backwardMostMove) {
15237         // [HGM] perhaps we should always do it like this, and forget the above?
15238         if((signed char)boards[move][EP_STATUS] >= 0) {
15239             *p++ = boards[move][EP_STATUS] + AAA;
15240             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15241         } else {
15242             *p++ = '-';
15243         }
15244     } else {
15245         *p++ = '-';
15246     }
15247     *p++ = ' ';
15248   }
15249   }
15250
15251     /* [HGM] find reversible plies */
15252     {   int i = 0, j=move;
15253
15254         if (appData.debugMode) { int k;
15255             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15256             for(k=backwardMostMove; k<=forwardMostMove; k++)
15257                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15258
15259         }
15260
15261         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15262         if( j == backwardMostMove ) i += initialRulePlies;
15263         sprintf(p, "%d ", i);
15264         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15265     }
15266     /* Fullmove number */
15267     sprintf(p, "%d", (move / 2) + 1);
15268
15269     return StrSave(buf);
15270 }
15271
15272 Boolean
15273 ParseFEN(board, blackPlaysFirst, fen)
15274     Board board;
15275      int *blackPlaysFirst;
15276      char *fen;
15277 {
15278     int i, j;
15279     char *p, c;
15280     int emptycount;
15281     ChessSquare piece;
15282
15283     p = fen;
15284
15285     /* [HGM] by default clear Crazyhouse holdings, if present */
15286     if(gameInfo.holdingsWidth) {
15287        for(i=0; i<BOARD_HEIGHT; i++) {
15288            board[i][0]             = EmptySquare; /* black holdings */
15289            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15290            board[i][1]             = (ChessSquare) 0; /* black counts */
15291            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15292        }
15293     }
15294
15295     /* Piece placement data */
15296     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15297         j = 0;
15298         for (;;) {
15299             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15300                 if (*p == '/') p++;
15301                 emptycount = gameInfo.boardWidth - j;
15302                 while (emptycount--)
15303                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15304                 break;
15305 #if(BOARD_FILES >= 10)
15306             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15307                 p++; emptycount=10;
15308                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15309                 while (emptycount--)
15310                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15311 #endif
15312             } else if (isdigit(*p)) {
15313                 emptycount = *p++ - '0';
15314                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15315                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15316                 while (emptycount--)
15317                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15318             } else if (*p == '+' || isalpha(*p)) {
15319                 if (j >= gameInfo.boardWidth) return FALSE;
15320                 if(*p=='+') {
15321                     piece = CharToPiece(*++p);
15322                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15323                     piece = (ChessSquare) (PROMOTED piece ); p++;
15324                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15325                 } else piece = CharToPiece(*p++);
15326
15327                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15328                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15329                     piece = (ChessSquare) (PROMOTED piece);
15330                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15331                     p++;
15332                 }
15333                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15334             } else {
15335                 return FALSE;
15336             }
15337         }
15338     }
15339     while (*p == '/' || *p == ' ') p++;
15340
15341     /* [HGM] look for Crazyhouse holdings here */
15342     while(*p==' ') p++;
15343     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15344         if(*p == '[') p++;
15345         if(*p == '-' ) p++; /* empty holdings */ else {
15346             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15347             /* if we would allow FEN reading to set board size, we would   */
15348             /* have to add holdings and shift the board read so far here   */
15349             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15350                 p++;
15351                 if((int) piece >= (int) BlackPawn ) {
15352                     i = (int)piece - (int)BlackPawn;
15353                     i = PieceToNumber((ChessSquare)i);
15354                     if( i >= gameInfo.holdingsSize ) return FALSE;
15355                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15356                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15357                 } else {
15358                     i = (int)piece - (int)WhitePawn;
15359                     i = PieceToNumber((ChessSquare)i);
15360                     if( i >= gameInfo.holdingsSize ) return FALSE;
15361                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15362                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15363                 }
15364             }
15365         }
15366         if(*p == ']') p++;
15367     }
15368
15369     while(*p == ' ') p++;
15370
15371     /* Active color */
15372     c = *p++;
15373     if(appData.colorNickNames) {
15374       if( c == appData.colorNickNames[0] ) c = 'w'; else
15375       if( c == appData.colorNickNames[1] ) c = 'b';
15376     }
15377     switch (c) {
15378       case 'w':
15379         *blackPlaysFirst = FALSE;
15380         break;
15381       case 'b':
15382         *blackPlaysFirst = TRUE;
15383         break;
15384       default:
15385         return FALSE;
15386     }
15387
15388     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15389     /* return the extra info in global variiables             */
15390
15391     /* set defaults in case FEN is incomplete */
15392     board[EP_STATUS] = EP_UNKNOWN;
15393     for(i=0; i<nrCastlingRights; i++ ) {
15394         board[CASTLING][i] =
15395             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15396     }   /* assume possible unless obviously impossible */
15397     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15398     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15399     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15400                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15401     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15402     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15403     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15404                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15405     FENrulePlies = 0;
15406
15407     while(*p==' ') p++;
15408     if(nrCastlingRights) {
15409       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15410           /* castling indicator present, so default becomes no castlings */
15411           for(i=0; i<nrCastlingRights; i++ ) {
15412                  board[CASTLING][i] = NoRights;
15413           }
15414       }
15415       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15416              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15417              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15418              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15419         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15420
15421         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15422             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15423             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15424         }
15425         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15426             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15427         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15428                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15429         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15430                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15431         switch(c) {
15432           case'K':
15433               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15434               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15435               board[CASTLING][2] = whiteKingFile;
15436               break;
15437           case'Q':
15438               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15439               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15440               board[CASTLING][2] = whiteKingFile;
15441               break;
15442           case'k':
15443               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15444               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15445               board[CASTLING][5] = blackKingFile;
15446               break;
15447           case'q':
15448               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15449               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15450               board[CASTLING][5] = blackKingFile;
15451           case '-':
15452               break;
15453           default: /* FRC castlings */
15454               if(c >= 'a') { /* black rights */
15455                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15456                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15457                   if(i == BOARD_RGHT) break;
15458                   board[CASTLING][5] = i;
15459                   c -= AAA;
15460                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15461                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15462                   if(c > i)
15463                       board[CASTLING][3] = c;
15464                   else
15465                       board[CASTLING][4] = c;
15466               } else { /* white rights */
15467                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15468                     if(board[0][i] == WhiteKing) break;
15469                   if(i == BOARD_RGHT) break;
15470                   board[CASTLING][2] = i;
15471                   c -= AAA - 'a' + 'A';
15472                   if(board[0][c] >= WhiteKing) break;
15473                   if(c > i)
15474                       board[CASTLING][0] = c;
15475                   else
15476                       board[CASTLING][1] = c;
15477               }
15478         }
15479       }
15480       for(i=0; i<nrCastlingRights; i++)
15481         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15482     if (appData.debugMode) {
15483         fprintf(debugFP, "FEN castling rights:");
15484         for(i=0; i<nrCastlingRights; i++)
15485         fprintf(debugFP, " %d", board[CASTLING][i]);
15486         fprintf(debugFP, "\n");
15487     }
15488
15489       while(*p==' ') p++;
15490     }
15491
15492     /* read e.p. field in games that know e.p. capture */
15493     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15494        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15495       if(*p=='-') {
15496         p++; board[EP_STATUS] = EP_NONE;
15497       } else {
15498          char c = *p++ - AAA;
15499
15500          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15501          if(*p >= '0' && *p <='9') p++;
15502          board[EP_STATUS] = c;
15503       }
15504     }
15505
15506
15507     if(sscanf(p, "%d", &i) == 1) {
15508         FENrulePlies = i; /* 50-move ply counter */
15509         /* (The move number is still ignored)    */
15510     }
15511
15512     return TRUE;
15513 }
15514
15515 void
15516 EditPositionPasteFEN(char *fen)
15517 {
15518   if (fen != NULL) {
15519     Board initial_position;
15520
15521     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15522       DisplayError(_("Bad FEN position in clipboard"), 0);
15523       return ;
15524     } else {
15525       int savedBlackPlaysFirst = blackPlaysFirst;
15526       EditPositionEvent();
15527       blackPlaysFirst = savedBlackPlaysFirst;
15528       CopyBoard(boards[0], initial_position);
15529       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15530       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15531       DisplayBothClocks();
15532       DrawPosition(FALSE, boards[currentMove]);
15533     }
15534   }
15535 }
15536
15537 static char cseq[12] = "\\   ";
15538
15539 Boolean set_cont_sequence(char *new_seq)
15540 {
15541     int len;
15542     Boolean ret;
15543
15544     // handle bad attempts to set the sequence
15545         if (!new_seq)
15546                 return 0; // acceptable error - no debug
15547
15548     len = strlen(new_seq);
15549     ret = (len > 0) && (len < sizeof(cseq));
15550     if (ret)
15551       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15552     else if (appData.debugMode)
15553       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15554     return ret;
15555 }
15556
15557 /*
15558     reformat a source message so words don't cross the width boundary.  internal
15559     newlines are not removed.  returns the wrapped size (no null character unless
15560     included in source message).  If dest is NULL, only calculate the size required
15561     for the dest buffer.  lp argument indicats line position upon entry, and it's
15562     passed back upon exit.
15563 */
15564 int wrap(char *dest, char *src, int count, int width, int *lp)
15565 {
15566     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15567
15568     cseq_len = strlen(cseq);
15569     old_line = line = *lp;
15570     ansi = len = clen = 0;
15571
15572     for (i=0; i < count; i++)
15573     {
15574         if (src[i] == '\033')
15575             ansi = 1;
15576
15577         // if we hit the width, back up
15578         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15579         {
15580             // store i & len in case the word is too long
15581             old_i = i, old_len = len;
15582
15583             // find the end of the last word
15584             while (i && src[i] != ' ' && src[i] != '\n')
15585             {
15586                 i--;
15587                 len--;
15588             }
15589
15590             // word too long?  restore i & len before splitting it
15591             if ((old_i-i+clen) >= width)
15592             {
15593                 i = old_i;
15594                 len = old_len;
15595             }
15596
15597             // extra space?
15598             if (i && src[i-1] == ' ')
15599                 len--;
15600
15601             if (src[i] != ' ' && src[i] != '\n')
15602             {
15603                 i--;
15604                 if (len)
15605                     len--;
15606             }
15607
15608             // now append the newline and continuation sequence
15609             if (dest)
15610                 dest[len] = '\n';
15611             len++;
15612             if (dest)
15613                 strncpy(dest+len, cseq, cseq_len);
15614             len += cseq_len;
15615             line = cseq_len;
15616             clen = cseq_len;
15617             continue;
15618         }
15619
15620         if (dest)
15621             dest[len] = src[i];
15622         len++;
15623         if (!ansi)
15624             line++;
15625         if (src[i] == '\n')
15626             line = 0;
15627         if (src[i] == 'm')
15628             ansi = 0;
15629     }
15630     if (dest && appData.debugMode)
15631     {
15632         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15633             count, width, line, len, *lp);
15634         show_bytes(debugFP, src, count);
15635         fprintf(debugFP, "\ndest: ");
15636         show_bytes(debugFP, dest, len);
15637         fprintf(debugFP, "\n");
15638     }
15639     *lp = dest ? line : old_line;
15640
15641     return len;
15642 }
15643
15644 // [HGM] vari: routines for shelving variations
15645
15646 void
15647 PushTail(int firstMove, int lastMove)
15648 {
15649         int i, j, nrMoves = lastMove - firstMove;
15650
15651         if(appData.icsActive) { // only in local mode
15652                 forwardMostMove = currentMove; // mimic old ICS behavior
15653                 return;
15654         }
15655         if(storedGames >= MAX_VARIATIONS-1) return;
15656
15657         // push current tail of game on stack
15658         savedResult[storedGames] = gameInfo.result;
15659         savedDetails[storedGames] = gameInfo.resultDetails;
15660         gameInfo.resultDetails = NULL;
15661         savedFirst[storedGames] = firstMove;
15662         savedLast [storedGames] = lastMove;
15663         savedFramePtr[storedGames] = framePtr;
15664         framePtr -= nrMoves; // reserve space for the boards
15665         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15666             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15667             for(j=0; j<MOVE_LEN; j++)
15668                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15669             for(j=0; j<2*MOVE_LEN; j++)
15670                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15671             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15672             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15673             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15674             pvInfoList[firstMove+i-1].depth = 0;
15675             commentList[framePtr+i] = commentList[firstMove+i];
15676             commentList[firstMove+i] = NULL;
15677         }
15678
15679         storedGames++;
15680         forwardMostMove = firstMove; // truncate game so we can start variation
15681         if(storedGames == 1) GreyRevert(FALSE);
15682 }
15683
15684 Boolean
15685 PopTail(Boolean annotate)
15686 {
15687         int i, j, nrMoves;
15688         char buf[8000], moveBuf[20];
15689
15690         if(appData.icsActive) return FALSE; // only in local mode
15691         if(!storedGames) return FALSE; // sanity
15692         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15693
15694         storedGames--;
15695         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15696         nrMoves = savedLast[storedGames] - currentMove;
15697         if(annotate) {
15698                 int cnt = 10;
15699                 if(!WhiteOnMove(currentMove))
15700                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15701                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15702                 for(i=currentMove; i<forwardMostMove; i++) {
15703                         if(WhiteOnMove(i))
15704                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15705                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15706                         strcat(buf, moveBuf);
15707                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15708                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15709                 }
15710                 strcat(buf, ")");
15711         }
15712         for(i=1; i<=nrMoves; i++) { // copy last variation back
15713             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15714             for(j=0; j<MOVE_LEN; j++)
15715                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15716             for(j=0; j<2*MOVE_LEN; j++)
15717                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15718             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15719             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15720             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15721             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15722             commentList[currentMove+i] = commentList[framePtr+i];
15723             commentList[framePtr+i] = NULL;
15724         }
15725         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15726         framePtr = savedFramePtr[storedGames];
15727         gameInfo.result = savedResult[storedGames];
15728         if(gameInfo.resultDetails != NULL) {
15729             free(gameInfo.resultDetails);
15730       }
15731         gameInfo.resultDetails = savedDetails[storedGames];
15732         forwardMostMove = currentMove + nrMoves;
15733         if(storedGames == 0) GreyRevert(TRUE);
15734         return TRUE;
15735 }
15736
15737 void
15738 CleanupTail()
15739 {       // remove all shelved variations
15740         int i;
15741         for(i=0; i<storedGames; i++) {
15742             if(savedDetails[i])
15743                 free(savedDetails[i]);
15744             savedDetails[i] = NULL;
15745         }
15746         for(i=framePtr; i<MAX_MOVES; i++) {
15747                 if(commentList[i]) free(commentList[i]);
15748                 commentList[i] = NULL;
15749         }
15750         framePtr = MAX_MOVES-1;
15751         storedGames = 0;
15752 }
15753
15754 void
15755 LoadVariation(int index, char *text)
15756 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15757         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15758         int level = 0, move;
15759
15760         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15761         // first find outermost bracketing variation
15762         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15763             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15764                 if(*p == '{') wait = '}'; else
15765                 if(*p == '[') wait = ']'; else
15766                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15767                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15768             }
15769             if(*p == wait) wait = NULLCHAR; // closing ]} found
15770             p++;
15771         }
15772         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15773         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15774         end[1] = NULLCHAR; // clip off comment beyond variation
15775         ToNrEvent(currentMove-1);
15776         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15777         // kludge: use ParsePV() to append variation to game
15778         move = currentMove;
15779         ParsePV(start, TRUE);
15780         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15781         ClearPremoveHighlights();
15782         CommentPopDown();
15783         ToNrEvent(currentMove+1);
15784 }
15785