d68df79eebe8977dc56ab0bce829ddb809d045c5
[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     if(n) return; // only startup first engine immediately; second can wait
845     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
846     LoadEngine();
847 }
848
849 void
850 InitBackEnd1()
851 {
852     int matched, min, sec;
853
854     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
855     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
856
857     GetTimeMark(&programStartTime);
858     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
859
860     ClearProgramStats();
861     programStats.ok_to_send = 1;
862     programStats.seen_stat = 0;
863
864     /*
865      * Initialize game list
866      */
867     ListNew(&gameList);
868
869
870     /*
871      * Internet chess server status
872      */
873     if (appData.icsActive) {
874         appData.matchMode = FALSE;
875         appData.matchGames = 0;
876 #if ZIPPY
877         appData.noChessProgram = !appData.zippyPlay;
878 #else
879         appData.zippyPlay = FALSE;
880         appData.zippyTalk = FALSE;
881         appData.noChessProgram = TRUE;
882 #endif
883         if (*appData.icsHelper != NULLCHAR) {
884             appData.useTelnet = TRUE;
885             appData.telnetProgram = appData.icsHelper;
886         }
887     } else {
888         appData.zippyTalk = appData.zippyPlay = FALSE;
889     }
890
891     /* [AS] Initialize pv info list [HGM] and game state */
892     {
893         int i, j;
894
895         for( i=0; i<=framePtr; i++ ) {
896             pvInfoList[i].depth = -1;
897             boards[i][EP_STATUS] = EP_NONE;
898             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
899         }
900     }
901
902     /*
903      * Parse timeControl resource
904      */
905     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
906                           appData.movesPerSession)) {
907         char buf[MSG_SIZ];
908         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
909         DisplayFatalError(buf, 0, 2);
910     }
911
912     /*
913      * Parse searchTime resource
914      */
915     if (*appData.searchTime != NULLCHAR) {
916         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
917         if (matched == 1) {
918             searchTime = min * 60;
919         } else if (matched == 2) {
920             searchTime = min * 60 + sec;
921         } else {
922             char buf[MSG_SIZ];
923             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
924             DisplayFatalError(buf, 0, 2);
925         }
926     }
927
928     /* [AS] Adjudication threshold */
929     adjudicateLossThreshold = appData.adjudicateLossThreshold;
930
931     InitEngine(&first, 0);
932     InitEngine(&second, 1);
933     CommonEngineInit();
934
935     if (appData.icsActive) {
936         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
937     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
938         appData.clockMode = FALSE;
939         first.sendTime = second.sendTime = 0;
940     }
941
942 #if ZIPPY
943     /* Override some settings from environment variables, for backward
944        compatibility.  Unfortunately it's not feasible to have the env
945        vars just set defaults, at least in xboard.  Ugh.
946     */
947     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
948       ZippyInit();
949     }
950 #endif
951
952     if (!appData.icsActive) {
953       char buf[MSG_SIZ];
954       int len;
955
956       /* Check for variants that are supported only in ICS mode,
957          or not at all.  Some that are accepted here nevertheless
958          have bugs; see comments below.
959       */
960       VariantClass variant = StringToVariant(appData.variant);
961       switch (variant) {
962       case VariantBughouse:     /* need four players and two boards */
963       case VariantKriegspiel:   /* need to hide pieces and move details */
964         /* case VariantFischeRandom: (Fabien: moved below) */
965         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
966         if( (len > MSG_SIZ) && appData.debugMode )
967           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
968
969         DisplayFatalError(buf, 0, 2);
970         return;
971
972       case VariantUnknown:
973       case VariantLoadable:
974       case Variant29:
975       case Variant30:
976       case Variant31:
977       case Variant32:
978       case Variant33:
979       case Variant34:
980       case Variant35:
981       case Variant36:
982       default:
983         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
984         if( (len > MSG_SIZ) && appData.debugMode )
985           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
986
987         DisplayFatalError(buf, 0, 2);
988         return;
989
990       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
991       case VariantFairy:      /* [HGM] TestLegality definitely off! */
992       case VariantGothic:     /* [HGM] should work */
993       case VariantCapablanca: /* [HGM] should work */
994       case VariantCourier:    /* [HGM] initial forced moves not implemented */
995       case VariantShogi:      /* [HGM] could still mate with pawn drop */
996       case VariantKnightmate: /* [HGM] should work */
997       case VariantCylinder:   /* [HGM] untested */
998       case VariantFalcon:     /* [HGM] untested */
999       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1000                                  offboard interposition not understood */
1001       case VariantNormal:     /* definitely works! */
1002       case VariantWildCastle: /* pieces not automatically shuffled */
1003       case VariantNoCastle:   /* pieces not automatically shuffled */
1004       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1005       case VariantLosers:     /* should work except for win condition,
1006                                  and doesn't know captures are mandatory */
1007       case VariantSuicide:    /* should work except for win condition,
1008                                  and doesn't know captures are mandatory */
1009       case VariantGiveaway:   /* should work except for win condition,
1010                                  and doesn't know captures are mandatory */
1011       case VariantTwoKings:   /* should work */
1012       case VariantAtomic:     /* should work except for win condition */
1013       case Variant3Check:     /* should work except for win condition */
1014       case VariantShatranj:   /* should work except for all win conditions */
1015       case VariantMakruk:     /* should work except for daw countdown */
1016       case VariantBerolina:   /* might work if TestLegality is off */
1017       case VariantCapaRandom: /* should work */
1018       case VariantJanus:      /* should work */
1019       case VariantSuper:      /* experimental */
1020       case VariantGreat:      /* experimental, requires legality testing to be off */
1021       case VariantSChess:     /* S-Chess, should work */
1022       case VariantSpartan:    /* should work */
1023         break;
1024       }
1025     }
1026
1027 }
1028
1029 int NextIntegerFromString( char ** str, long * value )
1030 {
1031     int result = -1;
1032     char * s = *str;
1033
1034     while( *s == ' ' || *s == '\t' ) {
1035         s++;
1036     }
1037
1038     *value = 0;
1039
1040     if( *s >= '0' && *s <= '9' ) {
1041         while( *s >= '0' && *s <= '9' ) {
1042             *value = *value * 10 + (*s - '0');
1043             s++;
1044         }
1045
1046         result = 0;
1047     }
1048
1049     *str = s;
1050
1051     return result;
1052 }
1053
1054 int NextTimeControlFromString( char ** str, long * value )
1055 {
1056     long temp;
1057     int result = NextIntegerFromString( str, &temp );
1058
1059     if( result == 0 ) {
1060         *value = temp * 60; /* Minutes */
1061         if( **str == ':' ) {
1062             (*str)++;
1063             result = NextIntegerFromString( str, &temp );
1064             *value += temp; /* Seconds */
1065         }
1066     }
1067
1068     return result;
1069 }
1070
1071 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1072 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1073     int result = -1, type = 0; long temp, temp2;
1074
1075     if(**str != ':') return -1; // old params remain in force!
1076     (*str)++;
1077     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1078     if( NextIntegerFromString( str, &temp ) ) return -1;
1079     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1080
1081     if(**str != '/') {
1082         /* time only: incremental or sudden-death time control */
1083         if(**str == '+') { /* increment follows; read it */
1084             (*str)++;
1085             if(**str == '!') type = *(*str)++; // Bronstein TC
1086             if(result = NextIntegerFromString( str, &temp2)) return -1;
1087             *inc = temp2 * 1000;
1088             if(**str == '.') { // read fraction of increment
1089                 char *start = ++(*str);
1090                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1091                 temp2 *= 1000;
1092                 while(start++ < *str) temp2 /= 10;
1093                 *inc += temp2;
1094             }
1095         } else *inc = 0;
1096         *moves = 0; *tc = temp * 1000; *incType = type;
1097         return 0;
1098     }
1099
1100     (*str)++; /* classical time control */
1101     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1102
1103     if(result == 0) {
1104         *moves = temp;
1105         *tc    = temp2 * 1000;
1106         *inc   = 0;
1107         *incType = type;
1108     }
1109     return result;
1110 }
1111
1112 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1113 {   /* [HGM] get time to add from the multi-session time-control string */
1114     int incType, moves=1; /* kludge to force reading of first session */
1115     long time, increment;
1116     char *s = tcString;
1117
1118     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1119     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1120     do {
1121         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1122         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1123         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1124         if(movenr == -1) return time;    /* last move before new session     */
1125         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1126         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1127         if(!moves) return increment;     /* current session is incremental   */
1128         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1129     } while(movenr >= -1);               /* try again for next session       */
1130
1131     return 0; // no new time quota on this move
1132 }
1133
1134 int
1135 ParseTimeControl(tc, ti, mps)
1136      char *tc;
1137      float ti;
1138      int mps;
1139 {
1140   long tc1;
1141   long tc2;
1142   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1143   int min, sec=0;
1144
1145   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1146   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1147       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1148   if(ti > 0) {
1149
1150     if(mps)
1151       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1152     else 
1153       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1154   } else {
1155     if(mps)
1156       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1157     else 
1158       snprintf(buf, MSG_SIZ, ":%s", mytc);
1159   }
1160   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1161   
1162   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1163     return FALSE;
1164   }
1165
1166   if( *tc == '/' ) {
1167     /* Parse second time control */
1168     tc++;
1169
1170     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1171       return FALSE;
1172     }
1173
1174     if( tc2 == 0 ) {
1175       return FALSE;
1176     }
1177
1178     timeControl_2 = tc2 * 1000;
1179   }
1180   else {
1181     timeControl_2 = 0;
1182   }
1183
1184   if( tc1 == 0 ) {
1185     return FALSE;
1186   }
1187
1188   timeControl = tc1 * 1000;
1189
1190   if (ti >= 0) {
1191     timeIncrement = ti * 1000;  /* convert to ms */
1192     movesPerSession = 0;
1193   } else {
1194     timeIncrement = 0;
1195     movesPerSession = mps;
1196   }
1197   return TRUE;
1198 }
1199
1200 void
1201 InitBackEnd2()
1202 {
1203     if (appData.debugMode) {
1204         fprintf(debugFP, "%s\n", programVersion);
1205     }
1206
1207     set_cont_sequence(appData.wrapContSeq);
1208     if (appData.matchGames > 0) {
1209         appData.matchMode = TRUE;
1210     } else if (appData.matchMode) {
1211         appData.matchGames = 1;
1212     }
1213     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1214         appData.matchGames = appData.sameColorGames;
1215     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1216         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1217         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1218     }
1219     Reset(TRUE, FALSE);
1220     if (appData.noChessProgram || first.protocolVersion == 1) {
1221       InitBackEnd3();
1222     } else {
1223       /* kludge: allow timeout for initial "feature" commands */
1224       FreezeUI();
1225       DisplayMessage("", _("Starting chess program"));
1226       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1227     }
1228 }
1229
1230 void
1231 MatchEvent(int mode)
1232 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1233         /* Set up machine vs. machine match */
1234         if (appData.noChessProgram) {
1235             DisplayFatalError(_("Can't have a match with no chess programs"),
1236                               0, 2);
1237             return;
1238         }
1239         matchMode = mode;
1240         matchGame = 1;
1241         if (*appData.loadGameFile != NULLCHAR) {
1242             int index = appData.loadGameIndex; // [HGM] autoinc
1243             if(index<0) lastIndex = index = 1;
1244             if (!LoadGameFromFile(appData.loadGameFile,
1245                                   index,
1246                                   appData.loadGameFile, FALSE)) {
1247                 DisplayFatalError(_("Bad game file"), 0, 1);
1248                 return;
1249             }
1250         } else if (*appData.loadPositionFile != NULLCHAR) {
1251             int index = appData.loadPositionIndex; // [HGM] autoinc
1252             if(index<0) lastIndex = index = 1;
1253             if (!LoadPositionFromFile(appData.loadPositionFile,
1254                                       index,
1255                                       appData.loadPositionFile)) {
1256                 DisplayFatalError(_("Bad position file"), 0, 1);
1257                 return;
1258             }
1259         }
1260         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1261         TwoMachinesEvent();
1262 }
1263
1264 void
1265 InitBackEnd3 P((void))
1266 {
1267     GameMode initialMode;
1268     char buf[MSG_SIZ];
1269     int err, len;
1270
1271     InitChessProgram(&first, startedFromSetupPosition);
1272
1273     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1274         free(programVersion);
1275         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1276         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1277     }
1278
1279     if (appData.icsActive) {
1280 #ifdef WIN32
1281         /* [DM] Make a console window if needed [HGM] merged ifs */
1282         ConsoleCreate();
1283 #endif
1284         err = establish();
1285         if (err != 0)
1286           {
1287             if (*appData.icsCommPort != NULLCHAR)
1288               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1289                              appData.icsCommPort);
1290             else
1291               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1292                         appData.icsHost, appData.icsPort);
1293
1294             if( (len > MSG_SIZ) && appData.debugMode )
1295               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1296
1297             DisplayFatalError(buf, err, 1);
1298             return;
1299         }
1300         SetICSMode();
1301         telnetISR =
1302           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1303         fromUserISR =
1304           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1305         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1306             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1307     } else if (appData.noChessProgram) {
1308         SetNCPMode();
1309     } else {
1310         SetGNUMode();
1311     }
1312
1313     if (*appData.cmailGameName != NULLCHAR) {
1314         SetCmailMode();
1315         OpenLoopback(&cmailPR);
1316         cmailISR =
1317           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1318     }
1319
1320     ThawUI();
1321     DisplayMessage("", "");
1322     if (StrCaseCmp(appData.initialMode, "") == 0) {
1323       initialMode = BeginningOfGame;
1324       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1325         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1326         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1327         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1328         ModeHighlight();
1329       }
1330     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1331       initialMode = TwoMachinesPlay;
1332     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1333       initialMode = AnalyzeFile;
1334     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1335       initialMode = AnalyzeMode;
1336     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1337       initialMode = MachinePlaysWhite;
1338     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1339       initialMode = MachinePlaysBlack;
1340     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1341       initialMode = EditGame;
1342     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1343       initialMode = EditPosition;
1344     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1345       initialMode = Training;
1346     } else {
1347       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1348       if( (len > MSG_SIZ) && appData.debugMode )
1349         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1350
1351       DisplayFatalError(buf, 0, 2);
1352       return;
1353     }
1354
1355     if (appData.matchMode) {
1356         MatchEvent(TRUE);
1357     } else if (*appData.cmailGameName != NULLCHAR) {
1358         /* Set up cmail mode */
1359         ReloadCmailMsgEvent(TRUE);
1360     } else {
1361         /* Set up other modes */
1362         if (initialMode == AnalyzeFile) {
1363           if (*appData.loadGameFile == NULLCHAR) {
1364             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1365             return;
1366           }
1367         }
1368         if (*appData.loadGameFile != NULLCHAR) {
1369             (void) LoadGameFromFile(appData.loadGameFile,
1370                                     appData.loadGameIndex,
1371                                     appData.loadGameFile, TRUE);
1372         } else if (*appData.loadPositionFile != NULLCHAR) {
1373             (void) LoadPositionFromFile(appData.loadPositionFile,
1374                                         appData.loadPositionIndex,
1375                                         appData.loadPositionFile);
1376             /* [HGM] try to make self-starting even after FEN load */
1377             /* to allow automatic setup of fairy variants with wtm */
1378             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1379                 gameMode = BeginningOfGame;
1380                 setboardSpoiledMachineBlack = 1;
1381             }
1382             /* [HGM] loadPos: make that every new game uses the setup */
1383             /* from file as long as we do not switch variant          */
1384             if(!blackPlaysFirst) {
1385                 startedFromPositionFile = TRUE;
1386                 CopyBoard(filePosition, boards[0]);
1387             }
1388         }
1389         if (initialMode == AnalyzeMode) {
1390           if (appData.noChessProgram) {
1391             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1392             return;
1393           }
1394           if (appData.icsActive) {
1395             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1396             return;
1397           }
1398           AnalyzeModeEvent();
1399         } else if (initialMode == AnalyzeFile) {
1400           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1401           ShowThinkingEvent();
1402           AnalyzeFileEvent();
1403           AnalysisPeriodicEvent(1);
1404         } else if (initialMode == MachinePlaysWhite) {
1405           if (appData.noChessProgram) {
1406             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1407                               0, 2);
1408             return;
1409           }
1410           if (appData.icsActive) {
1411             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1412                               0, 2);
1413             return;
1414           }
1415           MachineWhiteEvent();
1416         } else if (initialMode == MachinePlaysBlack) {
1417           if (appData.noChessProgram) {
1418             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1419                               0, 2);
1420             return;
1421           }
1422           if (appData.icsActive) {
1423             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1424                               0, 2);
1425             return;
1426           }
1427           MachineBlackEvent();
1428         } else if (initialMode == TwoMachinesPlay) {
1429           if (appData.noChessProgram) {
1430             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1431                               0, 2);
1432             return;
1433           }
1434           if (appData.icsActive) {
1435             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1436                               0, 2);
1437             return;
1438           }
1439           TwoMachinesEvent();
1440         } else if (initialMode == EditGame) {
1441           EditGameEvent();
1442         } else if (initialMode == EditPosition) {
1443           EditPositionEvent();
1444         } else if (initialMode == Training) {
1445           if (*appData.loadGameFile == NULLCHAR) {
1446             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1447             return;
1448           }
1449           TrainingEvent();
1450         }
1451     }
1452 }
1453
1454 /*
1455  * Establish will establish a contact to a remote host.port.
1456  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1457  *  used to talk to the host.
1458  * Returns 0 if okay, error code if not.
1459  */
1460 int
1461 establish()
1462 {
1463     char buf[MSG_SIZ];
1464
1465     if (*appData.icsCommPort != NULLCHAR) {
1466         /* Talk to the host through a serial comm port */
1467         return OpenCommPort(appData.icsCommPort, &icsPR);
1468
1469     } else if (*appData.gateway != NULLCHAR) {
1470         if (*appData.remoteShell == NULLCHAR) {
1471             /* Use the rcmd protocol to run telnet program on a gateway host */
1472             snprintf(buf, sizeof(buf), "%s %s %s",
1473                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1474             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1475
1476         } else {
1477             /* Use the rsh program to run telnet program on a gateway host */
1478             if (*appData.remoteUser == NULLCHAR) {
1479                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1480                         appData.gateway, appData.telnetProgram,
1481                         appData.icsHost, appData.icsPort);
1482             } else {
1483                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1484                         appData.remoteShell, appData.gateway,
1485                         appData.remoteUser, appData.telnetProgram,
1486                         appData.icsHost, appData.icsPort);
1487             }
1488             return StartChildProcess(buf, "", &icsPR);
1489
1490         }
1491     } else if (appData.useTelnet) {
1492         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1493
1494     } else {
1495         /* TCP socket interface differs somewhat between
1496            Unix and NT; handle details in the front end.
1497            */
1498         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1499     }
1500 }
1501
1502 void EscapeExpand(char *p, char *q)
1503 {       // [HGM] initstring: routine to shape up string arguments
1504         while(*p++ = *q++) if(p[-1] == '\\')
1505             switch(*q++) {
1506                 case 'n': p[-1] = '\n'; break;
1507                 case 'r': p[-1] = '\r'; break;
1508                 case 't': p[-1] = '\t'; break;
1509                 case '\\': p[-1] = '\\'; break;
1510                 case 0: *p = 0; return;
1511                 default: p[-1] = q[-1]; break;
1512             }
1513 }
1514
1515 void
1516 show_bytes(fp, buf, count)
1517      FILE *fp;
1518      char *buf;
1519      int count;
1520 {
1521     while (count--) {
1522         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1523             fprintf(fp, "\\%03o", *buf & 0xff);
1524         } else {
1525             putc(*buf, fp);
1526         }
1527         buf++;
1528     }
1529     fflush(fp);
1530 }
1531
1532 /* Returns an errno value */
1533 int
1534 OutputMaybeTelnet(pr, message, count, outError)
1535      ProcRef pr;
1536      char *message;
1537      int count;
1538      int *outError;
1539 {
1540     char buf[8192], *p, *q, *buflim;
1541     int left, newcount, outcount;
1542
1543     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1544         *appData.gateway != NULLCHAR) {
1545         if (appData.debugMode) {
1546             fprintf(debugFP, ">ICS: ");
1547             show_bytes(debugFP, message, count);
1548             fprintf(debugFP, "\n");
1549         }
1550         return OutputToProcess(pr, message, count, outError);
1551     }
1552
1553     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1554     p = message;
1555     q = buf;
1556     left = count;
1557     newcount = 0;
1558     while (left) {
1559         if (q >= buflim) {
1560             if (appData.debugMode) {
1561                 fprintf(debugFP, ">ICS: ");
1562                 show_bytes(debugFP, buf, newcount);
1563                 fprintf(debugFP, "\n");
1564             }
1565             outcount = OutputToProcess(pr, buf, newcount, outError);
1566             if (outcount < newcount) return -1; /* to be sure */
1567             q = buf;
1568             newcount = 0;
1569         }
1570         if (*p == '\n') {
1571             *q++ = '\r';
1572             newcount++;
1573         } else if (((unsigned char) *p) == TN_IAC) {
1574             *q++ = (char) TN_IAC;
1575             newcount ++;
1576         }
1577         *q++ = *p++;
1578         newcount++;
1579         left--;
1580     }
1581     if (appData.debugMode) {
1582         fprintf(debugFP, ">ICS: ");
1583         show_bytes(debugFP, buf, newcount);
1584         fprintf(debugFP, "\n");
1585     }
1586     outcount = OutputToProcess(pr, buf, newcount, outError);
1587     if (outcount < newcount) return -1; /* to be sure */
1588     return count;
1589 }
1590
1591 void
1592 read_from_player(isr, closure, message, count, error)
1593      InputSourceRef isr;
1594      VOIDSTAR closure;
1595      char *message;
1596      int count;
1597      int error;
1598 {
1599     int outError, outCount;
1600     static int gotEof = 0;
1601
1602     /* Pass data read from player on to ICS */
1603     if (count > 0) {
1604         gotEof = 0;
1605         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1606         if (outCount < count) {
1607             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1608         }
1609     } else if (count < 0) {
1610         RemoveInputSource(isr);
1611         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1612     } else if (gotEof++ > 0) {
1613         RemoveInputSource(isr);
1614         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1615     }
1616 }
1617
1618 void
1619 KeepAlive()
1620 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1621     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1622     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1623     SendToICS("date\n");
1624     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1625 }
1626
1627 /* added routine for printf style output to ics */
1628 void ics_printf(char *format, ...)
1629 {
1630     char buffer[MSG_SIZ];
1631     va_list args;
1632
1633     va_start(args, format);
1634     vsnprintf(buffer, sizeof(buffer), format, args);
1635     buffer[sizeof(buffer)-1] = '\0';
1636     SendToICS(buffer);
1637     va_end(args);
1638 }
1639
1640 void
1641 SendToICS(s)
1642      char *s;
1643 {
1644     int count, outCount, outError;
1645
1646     if (icsPR == NULL) return;
1647
1648     count = strlen(s);
1649     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1650     if (outCount < count) {
1651         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1652     }
1653 }
1654
1655 /* This is used for sending logon scripts to the ICS. Sending
1656    without a delay causes problems when using timestamp on ICC
1657    (at least on my machine). */
1658 void
1659 SendToICSDelayed(s,msdelay)
1660      char *s;
1661      long msdelay;
1662 {
1663     int count, outCount, outError;
1664
1665     if (icsPR == NULL) return;
1666
1667     count = strlen(s);
1668     if (appData.debugMode) {
1669         fprintf(debugFP, ">ICS: ");
1670         show_bytes(debugFP, s, count);
1671         fprintf(debugFP, "\n");
1672     }
1673     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1674                                       msdelay);
1675     if (outCount < count) {
1676         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1677     }
1678 }
1679
1680
1681 /* Remove all highlighting escape sequences in s
1682    Also deletes any suffix starting with '('
1683    */
1684 char *
1685 StripHighlightAndTitle(s)
1686      char *s;
1687 {
1688     static char retbuf[MSG_SIZ];
1689     char *p = retbuf;
1690
1691     while (*s != NULLCHAR) {
1692         while (*s == '\033') {
1693             while (*s != NULLCHAR && !isalpha(*s)) s++;
1694             if (*s != NULLCHAR) s++;
1695         }
1696         while (*s != NULLCHAR && *s != '\033') {
1697             if (*s == '(' || *s == '[') {
1698                 *p = NULLCHAR;
1699                 return retbuf;
1700             }
1701             *p++ = *s++;
1702         }
1703     }
1704     *p = NULLCHAR;
1705     return retbuf;
1706 }
1707
1708 /* Remove all highlighting escape sequences in s */
1709 char *
1710 StripHighlight(s)
1711      char *s;
1712 {
1713     static char retbuf[MSG_SIZ];
1714     char *p = retbuf;
1715
1716     while (*s != NULLCHAR) {
1717         while (*s == '\033') {
1718             while (*s != NULLCHAR && !isalpha(*s)) s++;
1719             if (*s != NULLCHAR) s++;
1720         }
1721         while (*s != NULLCHAR && *s != '\033') {
1722             *p++ = *s++;
1723         }
1724     }
1725     *p = NULLCHAR;
1726     return retbuf;
1727 }
1728
1729 char *variantNames[] = VARIANT_NAMES;
1730 char *
1731 VariantName(v)
1732      VariantClass v;
1733 {
1734     return variantNames[v];
1735 }
1736
1737
1738 /* Identify a variant from the strings the chess servers use or the
1739    PGN Variant tag names we use. */
1740 VariantClass
1741 StringToVariant(e)
1742      char *e;
1743 {
1744     char *p;
1745     int wnum = -1;
1746     VariantClass v = VariantNormal;
1747     int i, found = FALSE;
1748     char buf[MSG_SIZ];
1749     int len;
1750
1751     if (!e) return v;
1752
1753     /* [HGM] skip over optional board-size prefixes */
1754     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1755         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1756         while( *e++ != '_');
1757     }
1758
1759     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1760         v = VariantNormal;
1761         found = TRUE;
1762     } else
1763     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1764       if (StrCaseStr(e, variantNames[i])) {
1765         v = (VariantClass) i;
1766         found = TRUE;
1767         break;
1768       }
1769     }
1770
1771     if (!found) {
1772       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1773           || StrCaseStr(e, "wild/fr")
1774           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1775         v = VariantFischeRandom;
1776       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1777                  (i = 1, p = StrCaseStr(e, "w"))) {
1778         p += i;
1779         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1780         if (isdigit(*p)) {
1781           wnum = atoi(p);
1782         } else {
1783           wnum = -1;
1784         }
1785         switch (wnum) {
1786         case 0: /* FICS only, actually */
1787         case 1:
1788           /* Castling legal even if K starts on d-file */
1789           v = VariantWildCastle;
1790           break;
1791         case 2:
1792         case 3:
1793         case 4:
1794           /* Castling illegal even if K & R happen to start in
1795              normal positions. */
1796           v = VariantNoCastle;
1797           break;
1798         case 5:
1799         case 7:
1800         case 8:
1801         case 10:
1802         case 11:
1803         case 12:
1804         case 13:
1805         case 14:
1806         case 15:
1807         case 18:
1808         case 19:
1809           /* Castling legal iff K & R start in normal positions */
1810           v = VariantNormal;
1811           break;
1812         case 6:
1813         case 20:
1814         case 21:
1815           /* Special wilds for position setup; unclear what to do here */
1816           v = VariantLoadable;
1817           break;
1818         case 9:
1819           /* Bizarre ICC game */
1820           v = VariantTwoKings;
1821           break;
1822         case 16:
1823           v = VariantKriegspiel;
1824           break;
1825         case 17:
1826           v = VariantLosers;
1827           break;
1828         case 22:
1829           v = VariantFischeRandom;
1830           break;
1831         case 23:
1832           v = VariantCrazyhouse;
1833           break;
1834         case 24:
1835           v = VariantBughouse;
1836           break;
1837         case 25:
1838           v = Variant3Check;
1839           break;
1840         case 26:
1841           /* Not quite the same as FICS suicide! */
1842           v = VariantGiveaway;
1843           break;
1844         case 27:
1845           v = VariantAtomic;
1846           break;
1847         case 28:
1848           v = VariantShatranj;
1849           break;
1850
1851         /* Temporary names for future ICC types.  The name *will* change in
1852            the next xboard/WinBoard release after ICC defines it. */
1853         case 29:
1854           v = Variant29;
1855           break;
1856         case 30:
1857           v = Variant30;
1858           break;
1859         case 31:
1860           v = Variant31;
1861           break;
1862         case 32:
1863           v = Variant32;
1864           break;
1865         case 33:
1866           v = Variant33;
1867           break;
1868         case 34:
1869           v = Variant34;
1870           break;
1871         case 35:
1872           v = Variant35;
1873           break;
1874         case 36:
1875           v = Variant36;
1876           break;
1877         case 37:
1878           v = VariantShogi;
1879           break;
1880         case 38:
1881           v = VariantXiangqi;
1882           break;
1883         case 39:
1884           v = VariantCourier;
1885           break;
1886         case 40:
1887           v = VariantGothic;
1888           break;
1889         case 41:
1890           v = VariantCapablanca;
1891           break;
1892         case 42:
1893           v = VariantKnightmate;
1894           break;
1895         case 43:
1896           v = VariantFairy;
1897           break;
1898         case 44:
1899           v = VariantCylinder;
1900           break;
1901         case 45:
1902           v = VariantFalcon;
1903           break;
1904         case 46:
1905           v = VariantCapaRandom;
1906           break;
1907         case 47:
1908           v = VariantBerolina;
1909           break;
1910         case 48:
1911           v = VariantJanus;
1912           break;
1913         case 49:
1914           v = VariantSuper;
1915           break;
1916         case 50:
1917           v = VariantGreat;
1918           break;
1919         case -1:
1920           /* Found "wild" or "w" in the string but no number;
1921              must assume it's normal chess. */
1922           v = VariantNormal;
1923           break;
1924         default:
1925           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1926           if( (len > MSG_SIZ) && appData.debugMode )
1927             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1928
1929           DisplayError(buf, 0);
1930           v = VariantUnknown;
1931           break;
1932         }
1933       }
1934     }
1935     if (appData.debugMode) {
1936       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1937               e, wnum, VariantName(v));
1938     }
1939     return v;
1940 }
1941
1942 static int leftover_start = 0, leftover_len = 0;
1943 char star_match[STAR_MATCH_N][MSG_SIZ];
1944
1945 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1946    advance *index beyond it, and set leftover_start to the new value of
1947    *index; else return FALSE.  If pattern contains the character '*', it
1948    matches any sequence of characters not containing '\r', '\n', or the
1949    character following the '*' (if any), and the matched sequence(s) are
1950    copied into star_match.
1951    */
1952 int
1953 looking_at(buf, index, pattern)
1954      char *buf;
1955      int *index;
1956      char *pattern;
1957 {
1958     char *bufp = &buf[*index], *patternp = pattern;
1959     int star_count = 0;
1960     char *matchp = star_match[0];
1961
1962     for (;;) {
1963         if (*patternp == NULLCHAR) {
1964             *index = leftover_start = bufp - buf;
1965             *matchp = NULLCHAR;
1966             return TRUE;
1967         }
1968         if (*bufp == NULLCHAR) return FALSE;
1969         if (*patternp == '*') {
1970             if (*bufp == *(patternp + 1)) {
1971                 *matchp = NULLCHAR;
1972                 matchp = star_match[++star_count];
1973                 patternp += 2;
1974                 bufp++;
1975                 continue;
1976             } else if (*bufp == '\n' || *bufp == '\r') {
1977                 patternp++;
1978                 if (*patternp == NULLCHAR)
1979                   continue;
1980                 else
1981                   return FALSE;
1982             } else {
1983                 *matchp++ = *bufp++;
1984                 continue;
1985             }
1986         }
1987         if (*patternp != *bufp) return FALSE;
1988         patternp++;
1989         bufp++;
1990     }
1991 }
1992
1993 void
1994 SendToPlayer(data, length)
1995      char *data;
1996      int length;
1997 {
1998     int error, outCount;
1999     outCount = OutputToProcess(NoProc, data, length, &error);
2000     if (outCount < length) {
2001         DisplayFatalError(_("Error writing to display"), error, 1);
2002     }
2003 }
2004
2005 void
2006 PackHolding(packed, holding)
2007      char packed[];
2008      char *holding;
2009 {
2010     char *p = holding;
2011     char *q = packed;
2012     int runlength = 0;
2013     int curr = 9999;
2014     do {
2015         if (*p == curr) {
2016             runlength++;
2017         } else {
2018             switch (runlength) {
2019               case 0:
2020                 break;
2021               case 1:
2022                 *q++ = curr;
2023                 break;
2024               case 2:
2025                 *q++ = curr;
2026                 *q++ = curr;
2027                 break;
2028               default:
2029                 sprintf(q, "%d", runlength);
2030                 while (*q) q++;
2031                 *q++ = curr;
2032                 break;
2033             }
2034             runlength = 1;
2035             curr = *p;
2036         }
2037     } while (*p++);
2038     *q = NULLCHAR;
2039 }
2040
2041 /* Telnet protocol requests from the front end */
2042 void
2043 TelnetRequest(ddww, option)
2044      unsigned char ddww, option;
2045 {
2046     unsigned char msg[3];
2047     int outCount, outError;
2048
2049     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2050
2051     if (appData.debugMode) {
2052         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2053         switch (ddww) {
2054           case TN_DO:
2055             ddwwStr = "DO";
2056             break;
2057           case TN_DONT:
2058             ddwwStr = "DONT";
2059             break;
2060           case TN_WILL:
2061             ddwwStr = "WILL";
2062             break;
2063           case TN_WONT:
2064             ddwwStr = "WONT";
2065             break;
2066           default:
2067             ddwwStr = buf1;
2068             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2069             break;
2070         }
2071         switch (option) {
2072           case TN_ECHO:
2073             optionStr = "ECHO";
2074             break;
2075           default:
2076             optionStr = buf2;
2077             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2078             break;
2079         }
2080         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2081     }
2082     msg[0] = TN_IAC;
2083     msg[1] = ddww;
2084     msg[2] = option;
2085     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2086     if (outCount < 3) {
2087         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2088     }
2089 }
2090
2091 void
2092 DoEcho()
2093 {
2094     if (!appData.icsActive) return;
2095     TelnetRequest(TN_DO, TN_ECHO);
2096 }
2097
2098 void
2099 DontEcho()
2100 {
2101     if (!appData.icsActive) return;
2102     TelnetRequest(TN_DONT, TN_ECHO);
2103 }
2104
2105 void
2106 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2107 {
2108     /* put the holdings sent to us by the server on the board holdings area */
2109     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2110     char p;
2111     ChessSquare piece;
2112
2113     if(gameInfo.holdingsWidth < 2)  return;
2114     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2115         return; // prevent overwriting by pre-board holdings
2116
2117     if( (int)lowestPiece >= BlackPawn ) {
2118         holdingsColumn = 0;
2119         countsColumn = 1;
2120         holdingsStartRow = BOARD_HEIGHT-1;
2121         direction = -1;
2122     } else {
2123         holdingsColumn = BOARD_WIDTH-1;
2124         countsColumn = BOARD_WIDTH-2;
2125         holdingsStartRow = 0;
2126         direction = 1;
2127     }
2128
2129     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2130         board[i][holdingsColumn] = EmptySquare;
2131         board[i][countsColumn]   = (ChessSquare) 0;
2132     }
2133     while( (p=*holdings++) != NULLCHAR ) {
2134         piece = CharToPiece( ToUpper(p) );
2135         if(piece == EmptySquare) continue;
2136         /*j = (int) piece - (int) WhitePawn;*/
2137         j = PieceToNumber(piece);
2138         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2139         if(j < 0) continue;               /* should not happen */
2140         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2141         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2142         board[holdingsStartRow+j*direction][countsColumn]++;
2143     }
2144 }
2145
2146
2147 void
2148 VariantSwitch(Board board, VariantClass newVariant)
2149 {
2150    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2151    static Board oldBoard;
2152
2153    startedFromPositionFile = FALSE;
2154    if(gameInfo.variant == newVariant) return;
2155
2156    /* [HGM] This routine is called each time an assignment is made to
2157     * gameInfo.variant during a game, to make sure the board sizes
2158     * are set to match the new variant. If that means adding or deleting
2159     * holdings, we shift the playing board accordingly
2160     * This kludge is needed because in ICS observe mode, we get boards
2161     * of an ongoing game without knowing the variant, and learn about the
2162     * latter only later. This can be because of the move list we requested,
2163     * in which case the game history is refilled from the beginning anyway,
2164     * but also when receiving holdings of a crazyhouse game. In the latter
2165     * case we want to add those holdings to the already received position.
2166     */
2167
2168
2169    if (appData.debugMode) {
2170      fprintf(debugFP, "Switch board from %s to %s\n",
2171              VariantName(gameInfo.variant), VariantName(newVariant));
2172      setbuf(debugFP, NULL);
2173    }
2174    shuffleOpenings = 0;       /* [HGM] shuffle */
2175    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2176    switch(newVariant)
2177      {
2178      case VariantShogi:
2179        newWidth = 9;  newHeight = 9;
2180        gameInfo.holdingsSize = 7;
2181      case VariantBughouse:
2182      case VariantCrazyhouse:
2183        newHoldingsWidth = 2; break;
2184      case VariantGreat:
2185        newWidth = 10;
2186      case VariantSuper:
2187        newHoldingsWidth = 2;
2188        gameInfo.holdingsSize = 8;
2189        break;
2190      case VariantGothic:
2191      case VariantCapablanca:
2192      case VariantCapaRandom:
2193        newWidth = 10;
2194      default:
2195        newHoldingsWidth = gameInfo.holdingsSize = 0;
2196      };
2197
2198    if(newWidth  != gameInfo.boardWidth  ||
2199       newHeight != gameInfo.boardHeight ||
2200       newHoldingsWidth != gameInfo.holdingsWidth ) {
2201
2202      /* shift position to new playing area, if needed */
2203      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2204        for(i=0; i<BOARD_HEIGHT; i++)
2205          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2206            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2207              board[i][j];
2208        for(i=0; i<newHeight; i++) {
2209          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2210          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2211        }
2212      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2213        for(i=0; i<BOARD_HEIGHT; i++)
2214          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2215            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2216              board[i][j];
2217      }
2218      gameInfo.boardWidth  = newWidth;
2219      gameInfo.boardHeight = newHeight;
2220      gameInfo.holdingsWidth = newHoldingsWidth;
2221      gameInfo.variant = newVariant;
2222      InitDrawingSizes(-2, 0);
2223    } else gameInfo.variant = newVariant;
2224    CopyBoard(oldBoard, board);   // remember correctly formatted board
2225      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2226    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2227 }
2228
2229 static int loggedOn = FALSE;
2230
2231 /*-- Game start info cache: --*/
2232 int gs_gamenum;
2233 char gs_kind[MSG_SIZ];
2234 static char player1Name[128] = "";
2235 static char player2Name[128] = "";
2236 static char cont_seq[] = "\n\\   ";
2237 static int player1Rating = -1;
2238 static int player2Rating = -1;
2239 /*----------------------------*/
2240
2241 ColorClass curColor = ColorNormal;
2242 int suppressKibitz = 0;
2243
2244 // [HGM] seekgraph
2245 Boolean soughtPending = FALSE;
2246 Boolean seekGraphUp;
2247 #define MAX_SEEK_ADS 200
2248 #define SQUARE 0x80
2249 char *seekAdList[MAX_SEEK_ADS];
2250 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2251 float tcList[MAX_SEEK_ADS];
2252 char colorList[MAX_SEEK_ADS];
2253 int nrOfSeekAds = 0;
2254 int minRating = 1010, maxRating = 2800;
2255 int hMargin = 10, vMargin = 20, h, w;
2256 extern int squareSize, lineGap;
2257
2258 void
2259 PlotSeekAd(int i)
2260 {
2261         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2262         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2263         if(r < minRating+100 && r >=0 ) r = minRating+100;
2264         if(r > maxRating) r = maxRating;
2265         if(tc < 1.) tc = 1.;
2266         if(tc > 95.) tc = 95.;
2267         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2268         y = ((double)r - minRating)/(maxRating - minRating)
2269             * (h-vMargin-squareSize/8-1) + vMargin;
2270         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2271         if(strstr(seekAdList[i], " u ")) color = 1;
2272         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2273            !strstr(seekAdList[i], "bullet") &&
2274            !strstr(seekAdList[i], "blitz") &&
2275            !strstr(seekAdList[i], "standard") ) color = 2;
2276         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2277         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2278 }
2279
2280 void
2281 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2282 {
2283         char buf[MSG_SIZ], *ext = "";
2284         VariantClass v = StringToVariant(type);
2285         if(strstr(type, "wild")) {
2286             ext = type + 4; // append wild number
2287             if(v == VariantFischeRandom) type = "chess960"; else
2288             if(v == VariantLoadable) type = "setup"; else
2289             type = VariantName(v);
2290         }
2291         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2292         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2293             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2294             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2295             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2296             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2297             seekNrList[nrOfSeekAds] = nr;
2298             zList[nrOfSeekAds] = 0;
2299             seekAdList[nrOfSeekAds++] = StrSave(buf);
2300             if(plot) PlotSeekAd(nrOfSeekAds-1);
2301         }
2302 }
2303
2304 void
2305 EraseSeekDot(int i)
2306 {
2307     int x = xList[i], y = yList[i], d=squareSize/4, k;
2308     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2309     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2310     // now replot every dot that overlapped
2311     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2312         int xx = xList[k], yy = yList[k];
2313         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2314             DrawSeekDot(xx, yy, colorList[k]);
2315     }
2316 }
2317
2318 void
2319 RemoveSeekAd(int nr)
2320 {
2321         int i;
2322         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2323             EraseSeekDot(i);
2324             if(seekAdList[i]) free(seekAdList[i]);
2325             seekAdList[i] = seekAdList[--nrOfSeekAds];
2326             seekNrList[i] = seekNrList[nrOfSeekAds];
2327             ratingList[i] = ratingList[nrOfSeekAds];
2328             colorList[i]  = colorList[nrOfSeekAds];
2329             tcList[i] = tcList[nrOfSeekAds];
2330             xList[i]  = xList[nrOfSeekAds];
2331             yList[i]  = yList[nrOfSeekAds];
2332             zList[i]  = zList[nrOfSeekAds];
2333             seekAdList[nrOfSeekAds] = NULL;
2334             break;
2335         }
2336 }
2337
2338 Boolean
2339 MatchSoughtLine(char *line)
2340 {
2341     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2342     int nr, base, inc, u=0; char dummy;
2343
2344     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2345        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2346        (u=1) &&
2347        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2348         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2349         // match: compact and save the line
2350         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2351         return TRUE;
2352     }
2353     return FALSE;
2354 }
2355
2356 int
2357 DrawSeekGraph()
2358 {
2359     int i;
2360     if(!seekGraphUp) return FALSE;
2361     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2362     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2363
2364     DrawSeekBackground(0, 0, w, h);
2365     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2366     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2367     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2368         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2369         yy = h-1-yy;
2370         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2371         if(i%500 == 0) {
2372             char buf[MSG_SIZ];
2373             snprintf(buf, MSG_SIZ, "%d", i);
2374             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2375         }
2376     }
2377     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2378     for(i=1; i<100; i+=(i<10?1:5)) {
2379         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2380         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2381         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2382             char buf[MSG_SIZ];
2383             snprintf(buf, MSG_SIZ, "%d", i);
2384             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2385         }
2386     }
2387     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2388     return TRUE;
2389 }
2390
2391 int SeekGraphClick(ClickType click, int x, int y, int moving)
2392 {
2393     static int lastDown = 0, displayed = 0, lastSecond;
2394     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2395         if(click == Release || moving) return FALSE;
2396         nrOfSeekAds = 0;
2397         soughtPending = TRUE;
2398         SendToICS(ics_prefix);
2399         SendToICS("sought\n"); // should this be "sought all"?
2400     } else { // issue challenge based on clicked ad
2401         int dist = 10000; int i, closest = 0, second = 0;
2402         for(i=0; i<nrOfSeekAds; i++) {
2403             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2404             if(d < dist) { dist = d; closest = i; }
2405             second += (d - zList[i] < 120); // count in-range ads
2406             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2407         }
2408         if(dist < 120) {
2409             char buf[MSG_SIZ];
2410             second = (second > 1);
2411             if(displayed != closest || second != lastSecond) {
2412                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2413                 lastSecond = second; displayed = closest;
2414             }
2415             if(click == Press) {
2416                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2417                 lastDown = closest;
2418                 return TRUE;
2419             } // on press 'hit', only show info
2420             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2421             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2422             SendToICS(ics_prefix);
2423             SendToICS(buf);
2424             return TRUE; // let incoming board of started game pop down the graph
2425         } else if(click == Release) { // release 'miss' is ignored
2426             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2427             if(moving == 2) { // right up-click
2428                 nrOfSeekAds = 0; // refresh graph
2429                 soughtPending = TRUE;
2430                 SendToICS(ics_prefix);
2431                 SendToICS("sought\n"); // should this be "sought all"?
2432             }
2433             return TRUE;
2434         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2435         // press miss or release hit 'pop down' seek graph
2436         seekGraphUp = FALSE;
2437         DrawPosition(TRUE, NULL);
2438     }
2439     return TRUE;
2440 }
2441
2442 void
2443 read_from_ics(isr, closure, data, count, error)
2444      InputSourceRef isr;
2445      VOIDSTAR closure;
2446      char *data;
2447      int count;
2448      int error;
2449 {
2450 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2451 #define STARTED_NONE 0
2452 #define STARTED_MOVES 1
2453 #define STARTED_BOARD 2
2454 #define STARTED_OBSERVE 3
2455 #define STARTED_HOLDINGS 4
2456 #define STARTED_CHATTER 5
2457 #define STARTED_COMMENT 6
2458 #define STARTED_MOVES_NOHIDE 7
2459
2460     static int started = STARTED_NONE;
2461     static char parse[20000];
2462     static int parse_pos = 0;
2463     static char buf[BUF_SIZE + 1];
2464     static int firstTime = TRUE, intfSet = FALSE;
2465     static ColorClass prevColor = ColorNormal;
2466     static int savingComment = FALSE;
2467     static int cmatch = 0; // continuation sequence match
2468     char *bp;
2469     char str[MSG_SIZ];
2470     int i, oldi;
2471     int buf_len;
2472     int next_out;
2473     int tkind;
2474     int backup;    /* [DM] For zippy color lines */
2475     char *p;
2476     char talker[MSG_SIZ]; // [HGM] chat
2477     int channel;
2478
2479     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2480
2481     if (appData.debugMode) {
2482       if (!error) {
2483         fprintf(debugFP, "<ICS: ");
2484         show_bytes(debugFP, data, count);
2485         fprintf(debugFP, "\n");
2486       }
2487     }
2488
2489     if (appData.debugMode) { int f = forwardMostMove;
2490         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2491                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2492                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2493     }
2494     if (count > 0) {
2495         /* If last read ended with a partial line that we couldn't parse,
2496            prepend it to the new read and try again. */
2497         if (leftover_len > 0) {
2498             for (i=0; i<leftover_len; i++)
2499               buf[i] = buf[leftover_start + i];
2500         }
2501
2502     /* copy new characters into the buffer */
2503     bp = buf + leftover_len;
2504     buf_len=leftover_len;
2505     for (i=0; i<count; i++)
2506     {
2507         // ignore these
2508         if (data[i] == '\r')
2509             continue;
2510
2511         // join lines split by ICS?
2512         if (!appData.noJoin)
2513         {
2514             /*
2515                 Joining just consists of finding matches against the
2516                 continuation sequence, and discarding that sequence
2517                 if found instead of copying it.  So, until a match
2518                 fails, there's nothing to do since it might be the
2519                 complete sequence, and thus, something we don't want
2520                 copied.
2521             */
2522             if (data[i] == cont_seq[cmatch])
2523             {
2524                 cmatch++;
2525                 if (cmatch == strlen(cont_seq))
2526                 {
2527                     cmatch = 0; // complete match.  just reset the counter
2528
2529                     /*
2530                         it's possible for the ICS to not include the space
2531                         at the end of the last word, making our [correct]
2532                         join operation fuse two separate words.  the server
2533                         does this when the space occurs at the width setting.
2534                     */
2535                     if (!buf_len || buf[buf_len-1] != ' ')
2536                     {
2537                         *bp++ = ' ';
2538                         buf_len++;
2539                     }
2540                 }
2541                 continue;
2542             }
2543             else if (cmatch)
2544             {
2545                 /*
2546                     match failed, so we have to copy what matched before
2547                     falling through and copying this character.  In reality,
2548                     this will only ever be just the newline character, but
2549                     it doesn't hurt to be precise.
2550                 */
2551                 strncpy(bp, cont_seq, cmatch);
2552                 bp += cmatch;
2553                 buf_len += cmatch;
2554                 cmatch = 0;
2555             }
2556         }
2557
2558         // copy this char
2559         *bp++ = data[i];
2560         buf_len++;
2561     }
2562
2563         buf[buf_len] = NULLCHAR;
2564 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2565         next_out = 0;
2566         leftover_start = 0;
2567
2568         i = 0;
2569         while (i < buf_len) {
2570             /* Deal with part of the TELNET option negotiation
2571                protocol.  We refuse to do anything beyond the
2572                defaults, except that we allow the WILL ECHO option,
2573                which ICS uses to turn off password echoing when we are
2574                directly connected to it.  We reject this option
2575                if localLineEditing mode is on (always on in xboard)
2576                and we are talking to port 23, which might be a real
2577                telnet server that will try to keep WILL ECHO on permanently.
2578              */
2579             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2580                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2581                 unsigned char option;
2582                 oldi = i;
2583                 switch ((unsigned char) buf[++i]) {
2584                   case TN_WILL:
2585                     if (appData.debugMode)
2586                       fprintf(debugFP, "\n<WILL ");
2587                     switch (option = (unsigned char) buf[++i]) {
2588                       case TN_ECHO:
2589                         if (appData.debugMode)
2590                           fprintf(debugFP, "ECHO ");
2591                         /* Reply only if this is a change, according
2592                            to the protocol rules. */
2593                         if (remoteEchoOption) break;
2594                         if (appData.localLineEditing &&
2595                             atoi(appData.icsPort) == TN_PORT) {
2596                             TelnetRequest(TN_DONT, TN_ECHO);
2597                         } else {
2598                             EchoOff();
2599                             TelnetRequest(TN_DO, TN_ECHO);
2600                             remoteEchoOption = TRUE;
2601                         }
2602                         break;
2603                       default:
2604                         if (appData.debugMode)
2605                           fprintf(debugFP, "%d ", option);
2606                         /* Whatever this is, we don't want it. */
2607                         TelnetRequest(TN_DONT, option);
2608                         break;
2609                     }
2610                     break;
2611                   case TN_WONT:
2612                     if (appData.debugMode)
2613                       fprintf(debugFP, "\n<WONT ");
2614                     switch (option = (unsigned char) buf[++i]) {
2615                       case TN_ECHO:
2616                         if (appData.debugMode)
2617                           fprintf(debugFP, "ECHO ");
2618                         /* Reply only if this is a change, according
2619                            to the protocol rules. */
2620                         if (!remoteEchoOption) break;
2621                         EchoOn();
2622                         TelnetRequest(TN_DONT, TN_ECHO);
2623                         remoteEchoOption = FALSE;
2624                         break;
2625                       default:
2626                         if (appData.debugMode)
2627                           fprintf(debugFP, "%d ", (unsigned char) option);
2628                         /* Whatever this is, it must already be turned
2629                            off, because we never agree to turn on
2630                            anything non-default, so according to the
2631                            protocol rules, we don't reply. */
2632                         break;
2633                     }
2634                     break;
2635                   case TN_DO:
2636                     if (appData.debugMode)
2637                       fprintf(debugFP, "\n<DO ");
2638                     switch (option = (unsigned char) buf[++i]) {
2639                       default:
2640                         /* Whatever this is, we refuse to do it. */
2641                         if (appData.debugMode)
2642                           fprintf(debugFP, "%d ", option);
2643                         TelnetRequest(TN_WONT, option);
2644                         break;
2645                     }
2646                     break;
2647                   case TN_DONT:
2648                     if (appData.debugMode)
2649                       fprintf(debugFP, "\n<DONT ");
2650                     switch (option = (unsigned char) buf[++i]) {
2651                       default:
2652                         if (appData.debugMode)
2653                           fprintf(debugFP, "%d ", option);
2654                         /* Whatever this is, we are already not doing
2655                            it, because we never agree to do anything
2656                            non-default, so according to the protocol
2657                            rules, we don't reply. */
2658                         break;
2659                     }
2660                     break;
2661                   case TN_IAC:
2662                     if (appData.debugMode)
2663                       fprintf(debugFP, "\n<IAC ");
2664                     /* Doubled IAC; pass it through */
2665                     i--;
2666                     break;
2667                   default:
2668                     if (appData.debugMode)
2669                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2670                     /* Drop all other telnet commands on the floor */
2671                     break;
2672                 }
2673                 if (oldi > next_out)
2674                   SendToPlayer(&buf[next_out], oldi - next_out);
2675                 if (++i > next_out)
2676                   next_out = i;
2677                 continue;
2678             }
2679
2680             /* OK, this at least will *usually* work */
2681             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2682                 loggedOn = TRUE;
2683             }
2684
2685             if (loggedOn && !intfSet) {
2686                 if (ics_type == ICS_ICC) {
2687                   snprintf(str, MSG_SIZ,
2688                           "/set-quietly interface %s\n/set-quietly style 12\n",
2689                           programVersion);
2690                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2691                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2692                 } else if (ics_type == ICS_CHESSNET) {
2693                   snprintf(str, MSG_SIZ, "/style 12\n");
2694                 } else {
2695                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2696                   strcat(str, programVersion);
2697                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2698                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2699                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2700 #ifdef WIN32
2701                   strcat(str, "$iset nohighlight 1\n");
2702 #endif
2703                   strcat(str, "$iset lock 1\n$style 12\n");
2704                 }
2705                 SendToICS(str);
2706                 NotifyFrontendLogin();
2707                 intfSet = TRUE;
2708             }
2709
2710             if (started == STARTED_COMMENT) {
2711                 /* Accumulate characters in comment */
2712                 parse[parse_pos++] = buf[i];
2713                 if (buf[i] == '\n') {
2714                     parse[parse_pos] = NULLCHAR;
2715                     if(chattingPartner>=0) {
2716                         char mess[MSG_SIZ];
2717                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2718                         OutputChatMessage(chattingPartner, mess);
2719                         chattingPartner = -1;
2720                         next_out = i+1; // [HGM] suppress printing in ICS window
2721                     } else
2722                     if(!suppressKibitz) // [HGM] kibitz
2723                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2724                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2725                         int nrDigit = 0, nrAlph = 0, j;
2726                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2727                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2728                         parse[parse_pos] = NULLCHAR;
2729                         // try to be smart: if it does not look like search info, it should go to
2730                         // ICS interaction window after all, not to engine-output window.
2731                         for(j=0; j<parse_pos; j++) { // count letters and digits
2732                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2733                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2734                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2735                         }
2736                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2737                             int depth=0; float score;
2738                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2739                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2740                                 pvInfoList[forwardMostMove-1].depth = depth;
2741                                 pvInfoList[forwardMostMove-1].score = 100*score;
2742                             }
2743                             OutputKibitz(suppressKibitz, parse);
2744                         } else {
2745                             char tmp[MSG_SIZ];
2746                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2747                             SendToPlayer(tmp, strlen(tmp));
2748                         }
2749                         next_out = i+1; // [HGM] suppress printing in ICS window
2750                     }
2751                     started = STARTED_NONE;
2752                 } else {
2753                     /* Don't match patterns against characters in comment */
2754                     i++;
2755                     continue;
2756                 }
2757             }
2758             if (started == STARTED_CHATTER) {
2759                 if (buf[i] != '\n') {
2760                     /* Don't match patterns against characters in chatter */
2761                     i++;
2762                     continue;
2763                 }
2764                 started = STARTED_NONE;
2765                 if(suppressKibitz) next_out = i+1;
2766             }
2767
2768             /* Kludge to deal with rcmd protocol */
2769             if (firstTime && looking_at(buf, &i, "\001*")) {
2770                 DisplayFatalError(&buf[1], 0, 1);
2771                 continue;
2772             } else {
2773                 firstTime = FALSE;
2774             }
2775
2776             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2777                 ics_type = ICS_ICC;
2778                 ics_prefix = "/";
2779                 if (appData.debugMode)
2780                   fprintf(debugFP, "ics_type %d\n", ics_type);
2781                 continue;
2782             }
2783             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2784                 ics_type = ICS_FICS;
2785                 ics_prefix = "$";
2786                 if (appData.debugMode)
2787                   fprintf(debugFP, "ics_type %d\n", ics_type);
2788                 continue;
2789             }
2790             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2791                 ics_type = ICS_CHESSNET;
2792                 ics_prefix = "/";
2793                 if (appData.debugMode)
2794                   fprintf(debugFP, "ics_type %d\n", ics_type);
2795                 continue;
2796             }
2797
2798             if (!loggedOn &&
2799                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2800                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2801                  looking_at(buf, &i, "will be \"*\""))) {
2802               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2803               continue;
2804             }
2805
2806             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2807               char buf[MSG_SIZ];
2808               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2809               DisplayIcsInteractionTitle(buf);
2810               have_set_title = TRUE;
2811             }
2812
2813             /* skip finger notes */
2814             if (started == STARTED_NONE &&
2815                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2816                  (buf[i] == '1' && buf[i+1] == '0')) &&
2817                 buf[i+2] == ':' && buf[i+3] == ' ') {
2818               started = STARTED_CHATTER;
2819               i += 3;
2820               continue;
2821             }
2822
2823             oldi = i;
2824             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2825             if(appData.seekGraph) {
2826                 if(soughtPending && MatchSoughtLine(buf+i)) {
2827                     i = strstr(buf+i, "rated") - buf;
2828                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829                     next_out = leftover_start = i;
2830                     started = STARTED_CHATTER;
2831                     suppressKibitz = TRUE;
2832                     continue;
2833                 }
2834                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2835                         && looking_at(buf, &i, "* ads displayed")) {
2836                     soughtPending = FALSE;
2837                     seekGraphUp = TRUE;
2838                     DrawSeekGraph();
2839                     continue;
2840                 }
2841                 if(appData.autoRefresh) {
2842                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2843                         int s = (ics_type == ICS_ICC); // ICC format differs
2844                         if(seekGraphUp)
2845                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2846                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2847                         looking_at(buf, &i, "*% "); // eat prompt
2848                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2849                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2850                         next_out = i; // suppress
2851                         continue;
2852                     }
2853                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2854                         char *p = star_match[0];
2855                         while(*p) {
2856                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2857                             while(*p && *p++ != ' '); // next
2858                         }
2859                         looking_at(buf, &i, "*% "); // eat prompt
2860                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2861                         next_out = i;
2862                         continue;
2863                     }
2864                 }
2865             }
2866
2867             /* skip formula vars */
2868             if (started == STARTED_NONE &&
2869                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2870               started = STARTED_CHATTER;
2871               i += 3;
2872               continue;
2873             }
2874
2875             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2876             if (appData.autoKibitz && started == STARTED_NONE &&
2877                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2878                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2879                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2880                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2881                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2882                         suppressKibitz = TRUE;
2883                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = i;
2885                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2886                                 && (gameMode == IcsPlayingWhite)) ||
2887                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2888                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2889                             started = STARTED_CHATTER; // own kibitz we simply discard
2890                         else {
2891                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2892                             parse_pos = 0; parse[0] = NULLCHAR;
2893                             savingComment = TRUE;
2894                             suppressKibitz = gameMode != IcsObserving ? 2 :
2895                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2896                         }
2897                         continue;
2898                 } else
2899                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2900                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2901                          && atoi(star_match[0])) {
2902                     // suppress the acknowledgements of our own autoKibitz
2903                     char *p;
2904                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2905                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2906                     SendToPlayer(star_match[0], strlen(star_match[0]));
2907                     if(looking_at(buf, &i, "*% ")) // eat prompt
2908                         suppressKibitz = FALSE;
2909                     next_out = i;
2910                     continue;
2911                 }
2912             } // [HGM] kibitz: end of patch
2913
2914             // [HGM] chat: intercept tells by users for which we have an open chat window
2915             channel = -1;
2916             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2917                                            looking_at(buf, &i, "* whispers:") ||
2918                                            looking_at(buf, &i, "* kibitzes:") ||
2919                                            looking_at(buf, &i, "* shouts:") ||
2920                                            looking_at(buf, &i, "* c-shouts:") ||
2921                                            looking_at(buf, &i, "--> * ") ||
2922                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2923                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2924                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2925                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2926                 int p;
2927                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2928                 chattingPartner = -1;
2929
2930                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2931                 for(p=0; p<MAX_CHAT; p++) {
2932                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2933                     talker[0] = '['; strcat(talker, "] ");
2934                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2935                     chattingPartner = p; break;
2936                     }
2937                 } else
2938                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2939                 for(p=0; p<MAX_CHAT; p++) {
2940                     if(!strcmp("kibitzes", chatPartner[p])) {
2941                         talker[0] = '['; strcat(talker, "] ");
2942                         chattingPartner = p; break;
2943                     }
2944                 } else
2945                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2946                 for(p=0; p<MAX_CHAT; p++) {
2947                     if(!strcmp("whispers", chatPartner[p])) {
2948                         talker[0] = '['; strcat(talker, "] ");
2949                         chattingPartner = p; break;
2950                     }
2951                 } else
2952                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2953                   if(buf[i-8] == '-' && buf[i-3] == 't')
2954                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2955                     if(!strcmp("c-shouts", chatPartner[p])) {
2956                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2957                         chattingPartner = p; break;
2958                     }
2959                   }
2960                   if(chattingPartner < 0)
2961                   for(p=0; p<MAX_CHAT; p++) {
2962                     if(!strcmp("shouts", chatPartner[p])) {
2963                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2964                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2965                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2966                         chattingPartner = p; break;
2967                     }
2968                   }
2969                 }
2970                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2971                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2972                     talker[0] = 0; Colorize(ColorTell, FALSE);
2973                     chattingPartner = p; break;
2974                 }
2975                 if(chattingPartner<0) i = oldi; else {
2976                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2977                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2978                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2979                     started = STARTED_COMMENT;
2980                     parse_pos = 0; parse[0] = NULLCHAR;
2981                     savingComment = 3 + chattingPartner; // counts as TRUE
2982                     suppressKibitz = TRUE;
2983                     continue;
2984                 }
2985             } // [HGM] chat: end of patch
2986
2987           backup = i;
2988             if (appData.zippyTalk || appData.zippyPlay) {
2989                 /* [DM] Backup address for color zippy lines */
2990 #if ZIPPY
2991                if (loggedOn == TRUE)
2992                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2993                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2994 #endif
2995             } // [DM] 'else { ' deleted
2996                 if (
2997                     /* Regular tells and says */
2998                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2999                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3000                     looking_at(buf, &i, "* says: ") ||
3001                     /* Don't color "message" or "messages" output */
3002                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3003                     looking_at(buf, &i, "*. * at *:*: ") ||
3004                     looking_at(buf, &i, "--* (*:*): ") ||
3005                     /* Message notifications (same color as tells) */
3006                     looking_at(buf, &i, "* has left a message ") ||
3007                     looking_at(buf, &i, "* just sent you a message:\n") ||
3008                     /* Whispers and kibitzes */
3009                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3010                     looking_at(buf, &i, "* kibitzes: ") ||
3011                     /* Channel tells */
3012                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3013
3014                   if (tkind == 1 && strchr(star_match[0], ':')) {
3015                       /* Avoid "tells you:" spoofs in channels */
3016                      tkind = 3;
3017                   }
3018                   if (star_match[0][0] == NULLCHAR ||
3019                       strchr(star_match[0], ' ') ||
3020                       (tkind == 3 && strchr(star_match[1], ' '))) {
3021                     /* Reject bogus matches */
3022                     i = oldi;
3023                   } else {
3024                     if (appData.colorize) {
3025                       if (oldi > next_out) {
3026                         SendToPlayer(&buf[next_out], oldi - next_out);
3027                         next_out = oldi;
3028                       }
3029                       switch (tkind) {
3030                       case 1:
3031                         Colorize(ColorTell, FALSE);
3032                         curColor = ColorTell;
3033                         break;
3034                       case 2:
3035                         Colorize(ColorKibitz, FALSE);
3036                         curColor = ColorKibitz;
3037                         break;
3038                       case 3:
3039                         p = strrchr(star_match[1], '(');
3040                         if (p == NULL) {
3041                           p = star_match[1];
3042                         } else {
3043                           p++;
3044                         }
3045                         if (atoi(p) == 1) {
3046                           Colorize(ColorChannel1, FALSE);
3047                           curColor = ColorChannel1;
3048                         } else {
3049                           Colorize(ColorChannel, FALSE);
3050                           curColor = ColorChannel;
3051                         }
3052                         break;
3053                       case 5:
3054                         curColor = ColorNormal;
3055                         break;
3056                       }
3057                     }
3058                     if (started == STARTED_NONE && appData.autoComment &&
3059                         (gameMode == IcsObserving ||
3060                          gameMode == IcsPlayingWhite ||
3061                          gameMode == IcsPlayingBlack)) {
3062                       parse_pos = i - oldi;
3063                       memcpy(parse, &buf[oldi], parse_pos);
3064                       parse[parse_pos] = NULLCHAR;
3065                       started = STARTED_COMMENT;
3066                       savingComment = TRUE;
3067                     } else {
3068                       started = STARTED_CHATTER;
3069                       savingComment = FALSE;
3070                     }
3071                     loggedOn = TRUE;
3072                     continue;
3073                   }
3074                 }
3075
3076                 if (looking_at(buf, &i, "* s-shouts: ") ||
3077                     looking_at(buf, &i, "* c-shouts: ")) {
3078                     if (appData.colorize) {
3079                         if (oldi > next_out) {
3080                             SendToPlayer(&buf[next_out], oldi - next_out);
3081                             next_out = oldi;
3082                         }
3083                         Colorize(ColorSShout, FALSE);
3084                         curColor = ColorSShout;
3085                     }
3086                     loggedOn = TRUE;
3087                     started = STARTED_CHATTER;
3088                     continue;
3089                 }
3090
3091                 if (looking_at(buf, &i, "--->")) {
3092                     loggedOn = TRUE;
3093                     continue;
3094                 }
3095
3096                 if (looking_at(buf, &i, "* shouts: ") ||
3097                     looking_at(buf, &i, "--> ")) {
3098                     if (appData.colorize) {
3099                         if (oldi > next_out) {
3100                             SendToPlayer(&buf[next_out], oldi - next_out);
3101                             next_out = oldi;
3102                         }
3103                         Colorize(ColorShout, FALSE);
3104                         curColor = ColorShout;
3105                     }
3106                     loggedOn = TRUE;
3107                     started = STARTED_CHATTER;
3108                     continue;
3109                 }
3110
3111                 if (looking_at( buf, &i, "Challenge:")) {
3112                     if (appData.colorize) {
3113                         if (oldi > next_out) {
3114                             SendToPlayer(&buf[next_out], oldi - next_out);
3115                             next_out = oldi;
3116                         }
3117                         Colorize(ColorChallenge, FALSE);
3118                         curColor = ColorChallenge;
3119                     }
3120                     loggedOn = TRUE;
3121                     continue;
3122                 }
3123
3124                 if (looking_at(buf, &i, "* offers you") ||
3125                     looking_at(buf, &i, "* offers to be") ||
3126                     looking_at(buf, &i, "* would like to") ||
3127                     looking_at(buf, &i, "* requests to") ||
3128                     looking_at(buf, &i, "Your opponent offers") ||
3129                     looking_at(buf, &i, "Your opponent requests")) {
3130
3131                     if (appData.colorize) {
3132                         if (oldi > next_out) {
3133                             SendToPlayer(&buf[next_out], oldi - next_out);
3134                             next_out = oldi;
3135                         }
3136                         Colorize(ColorRequest, FALSE);
3137                         curColor = ColorRequest;
3138                     }
3139                     continue;
3140                 }
3141
3142                 if (looking_at(buf, &i, "* (*) seeking")) {
3143                     if (appData.colorize) {
3144                         if (oldi > next_out) {
3145                             SendToPlayer(&buf[next_out], oldi - next_out);
3146                             next_out = oldi;
3147                         }
3148                         Colorize(ColorSeek, FALSE);
3149                         curColor = ColorSeek;
3150                     }
3151                     continue;
3152             }
3153
3154           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3155
3156             if (looking_at(buf, &i, "\\   ")) {
3157                 if (prevColor != ColorNormal) {
3158                     if (oldi > next_out) {
3159                         SendToPlayer(&buf[next_out], oldi - next_out);
3160                         next_out = oldi;
3161                     }
3162                     Colorize(prevColor, TRUE);
3163                     curColor = prevColor;
3164                 }
3165                 if (savingComment) {
3166                     parse_pos = i - oldi;
3167                     memcpy(parse, &buf[oldi], parse_pos);
3168                     parse[parse_pos] = NULLCHAR;
3169                     started = STARTED_COMMENT;
3170                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3171                         chattingPartner = savingComment - 3; // kludge to remember the box
3172                 } else {
3173                     started = STARTED_CHATTER;
3174                 }
3175                 continue;
3176             }
3177
3178             if (looking_at(buf, &i, "Black Strength :") ||
3179                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3180                 looking_at(buf, &i, "<10>") ||
3181                 looking_at(buf, &i, "#@#")) {
3182                 /* Wrong board style */
3183                 loggedOn = TRUE;
3184                 SendToICS(ics_prefix);
3185                 SendToICS("set style 12\n");
3186                 SendToICS(ics_prefix);
3187                 SendToICS("refresh\n");
3188                 continue;
3189             }
3190
3191             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3192                 ICSInitScript();
3193                 have_sent_ICS_logon = 1;
3194                 continue;
3195             }
3196
3197             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3198                 (looking_at(buf, &i, "\n<12> ") ||
3199                  looking_at(buf, &i, "<12> "))) {
3200                 loggedOn = TRUE;
3201                 if (oldi > next_out) {
3202                     SendToPlayer(&buf[next_out], oldi - next_out);
3203                 }
3204                 next_out = i;
3205                 started = STARTED_BOARD;
3206                 parse_pos = 0;
3207                 continue;
3208             }
3209
3210             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3211                 looking_at(buf, &i, "<b1> ")) {
3212                 if (oldi > next_out) {
3213                     SendToPlayer(&buf[next_out], oldi - next_out);
3214                 }
3215                 next_out = i;
3216                 started = STARTED_HOLDINGS;
3217                 parse_pos = 0;
3218                 continue;
3219             }
3220
3221             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3222                 loggedOn = TRUE;
3223                 /* Header for a move list -- first line */
3224
3225                 switch (ics_getting_history) {
3226                   case H_FALSE:
3227                     switch (gameMode) {
3228                       case IcsIdle:
3229                       case BeginningOfGame:
3230                         /* User typed "moves" or "oldmoves" while we
3231                            were idle.  Pretend we asked for these
3232                            moves and soak them up so user can step
3233                            through them and/or save them.
3234                            */
3235                         Reset(FALSE, TRUE);
3236                         gameMode = IcsObserving;
3237                         ModeHighlight();
3238                         ics_gamenum = -1;
3239                         ics_getting_history = H_GOT_UNREQ_HEADER;
3240                         break;
3241                       case EditGame: /*?*/
3242                       case EditPosition: /*?*/
3243                         /* Should above feature work in these modes too? */
3244                         /* For now it doesn't */
3245                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3246                         break;
3247                       default:
3248                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3249                         break;
3250                     }
3251                     break;
3252                   case H_REQUESTED:
3253                     /* Is this the right one? */
3254                     if (gameInfo.white && gameInfo.black &&
3255                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3256                         strcmp(gameInfo.black, star_match[2]) == 0) {
3257                         /* All is well */
3258                         ics_getting_history = H_GOT_REQ_HEADER;
3259                     }
3260                     break;
3261                   case H_GOT_REQ_HEADER:
3262                   case H_GOT_UNREQ_HEADER:
3263                   case H_GOT_UNWANTED_HEADER:
3264                   case H_GETTING_MOVES:
3265                     /* Should not happen */
3266                     DisplayError(_("Error gathering move list: two headers"), 0);
3267                     ics_getting_history = H_FALSE;
3268                     break;
3269                 }
3270
3271                 /* Save player ratings into gameInfo if needed */
3272                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3273                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3274                     (gameInfo.whiteRating == -1 ||
3275                      gameInfo.blackRating == -1)) {
3276
3277                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3278                     gameInfo.blackRating = string_to_rating(star_match[3]);
3279                     if (appData.debugMode)
3280                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3281                               gameInfo.whiteRating, gameInfo.blackRating);
3282                 }
3283                 continue;
3284             }
3285
3286             if (looking_at(buf, &i,
3287               "* * match, initial time: * minute*, increment: * second")) {
3288                 /* Header for a move list -- second line */
3289                 /* Initial board will follow if this is a wild game */
3290                 if (gameInfo.event != NULL) free(gameInfo.event);
3291                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3292                 gameInfo.event = StrSave(str);
3293                 /* [HGM] we switched variant. Translate boards if needed. */
3294                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3295                 continue;
3296             }
3297
3298             if (looking_at(buf, &i, "Move  ")) {
3299                 /* Beginning of a move list */
3300                 switch (ics_getting_history) {
3301                   case H_FALSE:
3302                     /* Normally should not happen */
3303                     /* Maybe user hit reset while we were parsing */
3304                     break;
3305                   case H_REQUESTED:
3306                     /* Happens if we are ignoring a move list that is not
3307                      * the one we just requested.  Common if the user
3308                      * tries to observe two games without turning off
3309                      * getMoveList */
3310                     break;
3311                   case H_GETTING_MOVES:
3312                     /* Should not happen */
3313                     DisplayError(_("Error gathering move list: nested"), 0);
3314                     ics_getting_history = H_FALSE;
3315                     break;
3316                   case H_GOT_REQ_HEADER:
3317                     ics_getting_history = H_GETTING_MOVES;
3318                     started = STARTED_MOVES;
3319                     parse_pos = 0;
3320                     if (oldi > next_out) {
3321                         SendToPlayer(&buf[next_out], oldi - next_out);
3322                     }
3323                     break;
3324                   case H_GOT_UNREQ_HEADER:
3325                     ics_getting_history = H_GETTING_MOVES;
3326                     started = STARTED_MOVES_NOHIDE;
3327                     parse_pos = 0;
3328                     break;
3329                   case H_GOT_UNWANTED_HEADER:
3330                     ics_getting_history = H_FALSE;
3331                     break;
3332                 }
3333                 continue;
3334             }
3335
3336             if (looking_at(buf, &i, "% ") ||
3337                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3338                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3339                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3340                     soughtPending = FALSE;
3341                     seekGraphUp = TRUE;
3342                     DrawSeekGraph();
3343                 }
3344                 if(suppressKibitz) next_out = i;
3345                 savingComment = FALSE;
3346                 suppressKibitz = 0;
3347                 switch (started) {
3348                   case STARTED_MOVES:
3349                   case STARTED_MOVES_NOHIDE:
3350                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3351                     parse[parse_pos + i - oldi] = NULLCHAR;
3352                     ParseGameHistory(parse);
3353 #if ZIPPY
3354                     if (appData.zippyPlay && first.initDone) {
3355                         FeedMovesToProgram(&first, forwardMostMove);
3356                         if (gameMode == IcsPlayingWhite) {
3357                             if (WhiteOnMove(forwardMostMove)) {
3358                                 if (first.sendTime) {
3359                                   if (first.useColors) {
3360                                     SendToProgram("black\n", &first);
3361                                   }
3362                                   SendTimeRemaining(&first, TRUE);
3363                                 }
3364                                 if (first.useColors) {
3365                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3366                                 }
3367                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3368                                 first.maybeThinking = TRUE;
3369                             } else {
3370                                 if (first.usePlayother) {
3371                                   if (first.sendTime) {
3372                                     SendTimeRemaining(&first, TRUE);
3373                                   }
3374                                   SendToProgram("playother\n", &first);
3375                                   firstMove = FALSE;
3376                                 } else {
3377                                   firstMove = TRUE;
3378                                 }
3379                             }
3380                         } else if (gameMode == IcsPlayingBlack) {
3381                             if (!WhiteOnMove(forwardMostMove)) {
3382                                 if (first.sendTime) {
3383                                   if (first.useColors) {
3384                                     SendToProgram("white\n", &first);
3385                                   }
3386                                   SendTimeRemaining(&first, FALSE);
3387                                 }
3388                                 if (first.useColors) {
3389                                   SendToProgram("black\n", &first);
3390                                 }
3391                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3392                                 first.maybeThinking = TRUE;
3393                             } else {
3394                                 if (first.usePlayother) {
3395                                   if (first.sendTime) {
3396                                     SendTimeRemaining(&first, FALSE);
3397                                   }
3398                                   SendToProgram("playother\n", &first);
3399                                   firstMove = FALSE;
3400                                 } else {
3401                                   firstMove = TRUE;
3402                                 }
3403                             }
3404                         }
3405                     }
3406 #endif
3407                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3408                         /* Moves came from oldmoves or moves command
3409                            while we weren't doing anything else.
3410                            */
3411                         currentMove = forwardMostMove;
3412                         ClearHighlights();/*!!could figure this out*/
3413                         flipView = appData.flipView;
3414                         DrawPosition(TRUE, boards[currentMove]);
3415                         DisplayBothClocks();
3416                         snprintf(str, MSG_SIZ, "%s vs. %s",
3417                                 gameInfo.white, gameInfo.black);
3418                         DisplayTitle(str);
3419                         gameMode = IcsIdle;
3420                     } else {
3421                         /* Moves were history of an active game */
3422                         if (gameInfo.resultDetails != NULL) {
3423                             free(gameInfo.resultDetails);
3424                             gameInfo.resultDetails = NULL;
3425                         }
3426                     }
3427                     HistorySet(parseList, backwardMostMove,
3428                                forwardMostMove, currentMove-1);
3429                     DisplayMove(currentMove - 1);
3430                     if (started == STARTED_MOVES) next_out = i;
3431                     started = STARTED_NONE;
3432                     ics_getting_history = H_FALSE;
3433                     break;
3434
3435                   case STARTED_OBSERVE:
3436                     started = STARTED_NONE;
3437                     SendToICS(ics_prefix);
3438                     SendToICS("refresh\n");
3439                     break;
3440
3441                   default:
3442                     break;
3443                 }
3444                 if(bookHit) { // [HGM] book: simulate book reply
3445                     static char bookMove[MSG_SIZ]; // a bit generous?
3446
3447                     programStats.nodes = programStats.depth = programStats.time =
3448                     programStats.score = programStats.got_only_move = 0;
3449                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3450
3451                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3452                     strcat(bookMove, bookHit);
3453                     HandleMachineMove(bookMove, &first);
3454                 }
3455                 continue;
3456             }
3457
3458             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3459                  started == STARTED_HOLDINGS ||
3460                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3461                 /* Accumulate characters in move list or board */
3462                 parse[parse_pos++] = buf[i];
3463             }
3464
3465             /* Start of game messages.  Mostly we detect start of game
3466                when the first board image arrives.  On some versions
3467                of the ICS, though, we need to do a "refresh" after starting
3468                to observe in order to get the current board right away. */
3469             if (looking_at(buf, &i, "Adding game * to observation list")) {
3470                 started = STARTED_OBSERVE;
3471                 continue;
3472             }
3473
3474             /* Handle auto-observe */
3475             if (appData.autoObserve &&
3476                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3477                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3478                 char *player;
3479                 /* Choose the player that was highlighted, if any. */
3480                 if (star_match[0][0] == '\033' ||
3481                     star_match[1][0] != '\033') {
3482                     player = star_match[0];
3483                 } else {
3484                     player = star_match[2];
3485                 }
3486                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3487                         ics_prefix, StripHighlightAndTitle(player));
3488                 SendToICS(str);
3489
3490                 /* Save ratings from notify string */
3491                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3492                 player1Rating = string_to_rating(star_match[1]);
3493                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3494                 player2Rating = string_to_rating(star_match[3]);
3495
3496                 if (appData.debugMode)
3497                   fprintf(debugFP,
3498                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3499                           player1Name, player1Rating,
3500                           player2Name, player2Rating);
3501
3502                 continue;
3503             }
3504
3505             /* Deal with automatic examine mode after a game,
3506                and with IcsObserving -> IcsExamining transition */
3507             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3508                 looking_at(buf, &i, "has made you an examiner of game *")) {
3509
3510                 int gamenum = atoi(star_match[0]);
3511                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3512                     gamenum == ics_gamenum) {
3513                     /* We were already playing or observing this game;
3514                        no need to refetch history */
3515                     gameMode = IcsExamining;
3516                     if (pausing) {
3517                         pauseExamForwardMostMove = forwardMostMove;
3518                     } else if (currentMove < forwardMostMove) {
3519                         ForwardInner(forwardMostMove);
3520                     }
3521                 } else {
3522                     /* I don't think this case really can happen */
3523                     SendToICS(ics_prefix);
3524                     SendToICS("refresh\n");
3525                 }
3526                 continue;
3527             }
3528
3529             /* Error messages */
3530 //          if (ics_user_moved) {
3531             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3532                 if (looking_at(buf, &i, "Illegal move") ||
3533                     looking_at(buf, &i, "Not a legal move") ||
3534                     looking_at(buf, &i, "Your king is in check") ||
3535                     looking_at(buf, &i, "It isn't your turn") ||
3536                     looking_at(buf, &i, "It is not your move")) {
3537                     /* Illegal move */
3538                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3539                         currentMove = forwardMostMove-1;
3540                         DisplayMove(currentMove - 1); /* before DMError */
3541                         DrawPosition(FALSE, boards[currentMove]);
3542                         SwitchClocks(forwardMostMove-1); // [HGM] race
3543                         DisplayBothClocks();
3544                     }
3545                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3546                     ics_user_moved = 0;
3547                     continue;
3548                 }
3549             }
3550
3551             if (looking_at(buf, &i, "still have time") ||
3552                 looking_at(buf, &i, "not out of time") ||
3553                 looking_at(buf, &i, "either player is out of time") ||
3554                 looking_at(buf, &i, "has timeseal; checking")) {
3555                 /* We must have called his flag a little too soon */
3556                 whiteFlag = blackFlag = FALSE;
3557                 continue;
3558             }
3559
3560             if (looking_at(buf, &i, "added * seconds to") ||
3561                 looking_at(buf, &i, "seconds were added to")) {
3562                 /* Update the clocks */
3563                 SendToICS(ics_prefix);
3564                 SendToICS("refresh\n");
3565                 continue;
3566             }
3567
3568             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3569                 ics_clock_paused = TRUE;
3570                 StopClocks();
3571                 continue;
3572             }
3573
3574             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3575                 ics_clock_paused = FALSE;
3576                 StartClocks();
3577                 continue;
3578             }
3579
3580             /* Grab player ratings from the Creating: message.
3581                Note we have to check for the special case when
3582                the ICS inserts things like [white] or [black]. */
3583             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3584                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3585                 /* star_matches:
3586                    0    player 1 name (not necessarily white)
3587                    1    player 1 rating
3588                    2    empty, white, or black (IGNORED)
3589                    3    player 2 name (not necessarily black)
3590                    4    player 2 rating
3591
3592                    The names/ratings are sorted out when the game
3593                    actually starts (below).
3594                 */
3595                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3596                 player1Rating = string_to_rating(star_match[1]);
3597                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3598                 player2Rating = string_to_rating(star_match[4]);
3599
3600                 if (appData.debugMode)
3601                   fprintf(debugFP,
3602                           "Ratings from 'Creating:' %s %d, %s %d\n",
3603                           player1Name, player1Rating,
3604                           player2Name, player2Rating);
3605
3606                 continue;
3607             }
3608
3609             /* Improved generic start/end-of-game messages */
3610             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3611                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3612                 /* If tkind == 0: */
3613                 /* star_match[0] is the game number */
3614                 /*           [1] is the white player's name */
3615                 /*           [2] is the black player's name */
3616                 /* For end-of-game: */
3617                 /*           [3] is the reason for the game end */
3618                 /*           [4] is a PGN end game-token, preceded by " " */
3619                 /* For start-of-game: */
3620                 /*           [3] begins with "Creating" or "Continuing" */
3621                 /*           [4] is " *" or empty (don't care). */
3622                 int gamenum = atoi(star_match[0]);
3623                 char *whitename, *blackname, *why, *endtoken;
3624                 ChessMove endtype = EndOfFile;
3625
3626                 if (tkind == 0) {
3627                   whitename = star_match[1];
3628                   blackname = star_match[2];
3629                   why = star_match[3];
3630                   endtoken = star_match[4];
3631                 } else {
3632                   whitename = star_match[1];
3633                   blackname = star_match[3];
3634                   why = star_match[5];
3635                   endtoken = star_match[6];
3636                 }
3637
3638                 /* Game start messages */
3639                 if (strncmp(why, "Creating ", 9) == 0 ||
3640                     strncmp(why, "Continuing ", 11) == 0) {
3641                     gs_gamenum = gamenum;
3642                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3643                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3644 #if ZIPPY
3645                     if (appData.zippyPlay) {
3646                         ZippyGameStart(whitename, blackname);
3647                     }
3648 #endif /*ZIPPY*/
3649                     partnerBoardValid = FALSE; // [HGM] bughouse
3650                     continue;
3651                 }
3652
3653                 /* Game end messages */
3654                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3655                     ics_gamenum != gamenum) {
3656                     continue;
3657                 }
3658                 while (endtoken[0] == ' ') endtoken++;
3659                 switch (endtoken[0]) {
3660                   case '*':
3661                   default:
3662                     endtype = GameUnfinished;
3663                     break;
3664                   case '0':
3665                     endtype = BlackWins;
3666                     break;
3667                   case '1':
3668                     if (endtoken[1] == '/')
3669                       endtype = GameIsDrawn;
3670                     else
3671                       endtype = WhiteWins;
3672                     break;
3673                 }
3674                 GameEnds(endtype, why, GE_ICS);
3675 #if ZIPPY
3676                 if (appData.zippyPlay && first.initDone) {
3677                     ZippyGameEnd(endtype, why);
3678                     if (first.pr == NULL) {
3679                       /* Start the next process early so that we'll
3680                          be ready for the next challenge */
3681                       StartChessProgram(&first);
3682                     }
3683                     /* Send "new" early, in case this command takes
3684                        a long time to finish, so that we'll be ready
3685                        for the next challenge. */
3686                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3687                     Reset(TRUE, TRUE);
3688                 }
3689 #endif /*ZIPPY*/
3690                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3691                 continue;
3692             }
3693
3694             if (looking_at(buf, &i, "Removing game * from observation") ||
3695                 looking_at(buf, &i, "no longer observing game *") ||
3696                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3697                 if (gameMode == IcsObserving &&
3698                     atoi(star_match[0]) == ics_gamenum)
3699                   {
3700                       /* icsEngineAnalyze */
3701                       if (appData.icsEngineAnalyze) {
3702                             ExitAnalyzeMode();
3703                             ModeHighlight();
3704                       }
3705                       StopClocks();
3706                       gameMode = IcsIdle;
3707                       ics_gamenum = -1;
3708                       ics_user_moved = FALSE;
3709                   }
3710                 continue;
3711             }
3712
3713             if (looking_at(buf, &i, "no longer examining game *")) {
3714                 if (gameMode == IcsExamining &&
3715                     atoi(star_match[0]) == ics_gamenum)
3716                   {
3717                       gameMode = IcsIdle;
3718                       ics_gamenum = -1;
3719                       ics_user_moved = FALSE;
3720                   }
3721                 continue;
3722             }
3723
3724             /* Advance leftover_start past any newlines we find,
3725                so only partial lines can get reparsed */
3726             if (looking_at(buf, &i, "\n")) {
3727                 prevColor = curColor;
3728                 if (curColor != ColorNormal) {
3729                     if (oldi > next_out) {
3730                         SendToPlayer(&buf[next_out], oldi - next_out);
3731                         next_out = oldi;
3732                     }
3733                     Colorize(ColorNormal, FALSE);
3734                     curColor = ColorNormal;
3735                 }
3736                 if (started == STARTED_BOARD) {
3737                     started = STARTED_NONE;
3738                     parse[parse_pos] = NULLCHAR;
3739                     ParseBoard12(parse);
3740                     ics_user_moved = 0;
3741
3742                     /* Send premove here */
3743                     if (appData.premove) {
3744                       char str[MSG_SIZ];
3745                       if (currentMove == 0 &&
3746                           gameMode == IcsPlayingWhite &&
3747                           appData.premoveWhite) {
3748                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3749                         if (appData.debugMode)
3750                           fprintf(debugFP, "Sending premove:\n");
3751                         SendToICS(str);
3752                       } else if (currentMove == 1 &&
3753                                  gameMode == IcsPlayingBlack &&
3754                                  appData.premoveBlack) {
3755                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3756                         if (appData.debugMode)
3757                           fprintf(debugFP, "Sending premove:\n");
3758                         SendToICS(str);
3759                       } else if (gotPremove) {
3760                         gotPremove = 0;
3761                         ClearPremoveHighlights();
3762                         if (appData.debugMode)
3763                           fprintf(debugFP, "Sending premove:\n");
3764                           UserMoveEvent(premoveFromX, premoveFromY,
3765                                         premoveToX, premoveToY,
3766                                         premovePromoChar);
3767                       }
3768                     }
3769
3770                     /* Usually suppress following prompt */
3771                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3772                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3773                         if (looking_at(buf, &i, "*% ")) {
3774                             savingComment = FALSE;
3775                             suppressKibitz = 0;
3776                         }
3777                     }
3778                     next_out = i;
3779                 } else if (started == STARTED_HOLDINGS) {
3780                     int gamenum;
3781                     char new_piece[MSG_SIZ];
3782                     started = STARTED_NONE;
3783                     parse[parse_pos] = NULLCHAR;
3784                     if (appData.debugMode)
3785                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3786                                                         parse, currentMove);
3787                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3788                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3789                         if (gameInfo.variant == VariantNormal) {
3790                           /* [HGM] We seem to switch variant during a game!
3791                            * Presumably no holdings were displayed, so we have
3792                            * to move the position two files to the right to
3793                            * create room for them!
3794                            */
3795                           VariantClass newVariant;
3796                           switch(gameInfo.boardWidth) { // base guess on board width
3797                                 case 9:  newVariant = VariantShogi; break;
3798                                 case 10: newVariant = VariantGreat; break;
3799                                 default: newVariant = VariantCrazyhouse; break;
3800                           }
3801                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3802                           /* Get a move list just to see the header, which
3803                              will tell us whether this is really bug or zh */
3804                           if (ics_getting_history == H_FALSE) {
3805                             ics_getting_history = H_REQUESTED;
3806                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3807                             SendToICS(str);
3808                           }
3809                         }
3810                         new_piece[0] = NULLCHAR;
3811                         sscanf(parse, "game %d white [%s black [%s <- %s",
3812                                &gamenum, white_holding, black_holding,
3813                                new_piece);
3814                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3815                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3816                         /* [HGM] copy holdings to board holdings area */
3817                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3818                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3819                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3820 #if ZIPPY
3821                         if (appData.zippyPlay && first.initDone) {
3822                             ZippyHoldings(white_holding, black_holding,
3823                                           new_piece);
3824                         }
3825 #endif /*ZIPPY*/
3826                         if (tinyLayout || smallLayout) {
3827                             char wh[16], bh[16];
3828                             PackHolding(wh, white_holding);
3829                             PackHolding(bh, black_holding);
3830                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3831                                     gameInfo.white, gameInfo.black);
3832                         } else {
3833                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3834                                     gameInfo.white, white_holding,
3835                                     gameInfo.black, black_holding);
3836                         }
3837                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3838                         DrawPosition(FALSE, boards[currentMove]);
3839                         DisplayTitle(str);
3840                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3841                         sscanf(parse, "game %d white [%s black [%s <- %s",
3842                                &gamenum, white_holding, black_holding,
3843                                new_piece);
3844                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3845                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3846                         /* [HGM] copy holdings to partner-board holdings area */
3847                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3848                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3849                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3850                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3851                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3852                       }
3853                     }
3854                     /* Suppress following prompt */
3855                     if (looking_at(buf, &i, "*% ")) {
3856                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3857                         savingComment = FALSE;
3858                         suppressKibitz = 0;
3859                     }
3860                     next_out = i;
3861                 }
3862                 continue;
3863             }
3864
3865             i++;                /* skip unparsed character and loop back */
3866         }
3867
3868         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3869 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3870 //          SendToPlayer(&buf[next_out], i - next_out);
3871             started != STARTED_HOLDINGS && leftover_start > next_out) {
3872             SendToPlayer(&buf[next_out], leftover_start - next_out);
3873             next_out = i;
3874         }
3875
3876         leftover_len = buf_len - leftover_start;
3877         /* if buffer ends with something we couldn't parse,
3878            reparse it after appending the next read */
3879
3880     } else if (count == 0) {
3881         RemoveInputSource(isr);
3882         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3883     } else {
3884         DisplayFatalError(_("Error reading from ICS"), error, 1);
3885     }
3886 }
3887
3888
3889 /* Board style 12 looks like this:
3890
3891    <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
3892
3893  * The "<12> " is stripped before it gets to this routine.  The two
3894  * trailing 0's (flip state and clock ticking) are later addition, and
3895  * some chess servers may not have them, or may have only the first.
3896  * Additional trailing fields may be added in the future.
3897  */
3898
3899 #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"
3900
3901 #define RELATION_OBSERVING_PLAYED    0
3902 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3903 #define RELATION_PLAYING_MYMOVE      1
3904 #define RELATION_PLAYING_NOTMYMOVE  -1
3905 #define RELATION_EXAMINING           2
3906 #define RELATION_ISOLATED_BOARD     -3
3907 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3908
3909 void
3910 ParseBoard12(string)
3911      char *string;
3912 {
3913     GameMode newGameMode;
3914     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3915     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3916     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3917     char to_play, board_chars[200];
3918     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3919     char black[32], white[32];
3920     Board board;
3921     int prevMove = currentMove;
3922     int ticking = 2;
3923     ChessMove moveType;
3924     int fromX, fromY, toX, toY;
3925     char promoChar;
3926     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3927     char *bookHit = NULL; // [HGM] book
3928     Boolean weird = FALSE, reqFlag = FALSE;
3929
3930     fromX = fromY = toX = toY = -1;
3931
3932     newGame = FALSE;
3933
3934     if (appData.debugMode)
3935       fprintf(debugFP, _("Parsing board: %s\n"), string);
3936
3937     move_str[0] = NULLCHAR;
3938     elapsed_time[0] = NULLCHAR;
3939     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3940         int  i = 0, j;
3941         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3942             if(string[i] == ' ') { ranks++; files = 0; }
3943             else files++;
3944             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3945             i++;
3946         }
3947         for(j = 0; j <i; j++) board_chars[j] = string[j];
3948         board_chars[i] = '\0';
3949         string += i + 1;
3950     }
3951     n = sscanf(string, PATTERN, &to_play, &double_push,
3952                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3953                &gamenum, white, black, &relation, &basetime, &increment,
3954                &white_stren, &black_stren, &white_time, &black_time,
3955                &moveNum, str, elapsed_time, move_str, &ics_flip,
3956                &ticking);
3957
3958     if (n < 21) {
3959         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3960         DisplayError(str, 0);
3961         return;
3962     }
3963
3964     /* Convert the move number to internal form */
3965     moveNum = (moveNum - 1) * 2;
3966     if (to_play == 'B') moveNum++;
3967     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3968       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3969                         0, 1);
3970       return;
3971     }
3972
3973     switch (relation) {
3974       case RELATION_OBSERVING_PLAYED:
3975       case RELATION_OBSERVING_STATIC:
3976         if (gamenum == -1) {
3977             /* Old ICC buglet */
3978             relation = RELATION_OBSERVING_STATIC;
3979         }
3980         newGameMode = IcsObserving;
3981         break;
3982       case RELATION_PLAYING_MYMOVE:
3983       case RELATION_PLAYING_NOTMYMOVE:
3984         newGameMode =
3985           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3986             IcsPlayingWhite : IcsPlayingBlack;
3987         break;
3988       case RELATION_EXAMINING:
3989         newGameMode = IcsExamining;
3990         break;
3991       case RELATION_ISOLATED_BOARD:
3992       default:
3993         /* Just display this board.  If user was doing something else,
3994            we will forget about it until the next board comes. */
3995         newGameMode = IcsIdle;
3996         break;
3997       case RELATION_STARTING_POSITION:
3998         newGameMode = gameMode;
3999         break;
4000     }
4001
4002     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4003          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4004       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4005       char *toSqr;
4006       for (k = 0; k < ranks; k++) {
4007         for (j = 0; j < files; j++)
4008           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4009         if(gameInfo.holdingsWidth > 1) {
4010              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4011              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4012         }
4013       }
4014       CopyBoard(partnerBoard, board);
4015       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4016         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4017         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4018       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4019       if(toSqr = strchr(str, '-')) {
4020         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4021         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4022       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4023       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4024       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4025       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4026       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4027       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4028                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4029       DisplayMessage(partnerStatus, "");
4030         partnerBoardValid = TRUE;
4031       return;
4032     }
4033
4034     /* Modify behavior for initial board display on move listing
4035        of wild games.
4036        */
4037     switch (ics_getting_history) {
4038       case H_FALSE:
4039       case H_REQUESTED:
4040         break;
4041       case H_GOT_REQ_HEADER:
4042       case H_GOT_UNREQ_HEADER:
4043         /* This is the initial position of the current game */
4044         gamenum = ics_gamenum;
4045         moveNum = 0;            /* old ICS bug workaround */
4046         if (to_play == 'B') {
4047           startedFromSetupPosition = TRUE;
4048           blackPlaysFirst = TRUE;
4049           moveNum = 1;
4050           if (forwardMostMove == 0) forwardMostMove = 1;
4051           if (backwardMostMove == 0) backwardMostMove = 1;
4052           if (currentMove == 0) currentMove = 1;
4053         }
4054         newGameMode = gameMode;
4055         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4056         break;
4057       case H_GOT_UNWANTED_HEADER:
4058         /* This is an initial board that we don't want */
4059         return;
4060       case H_GETTING_MOVES:
4061         /* Should not happen */
4062         DisplayError(_("Error gathering move list: extra board"), 0);
4063         ics_getting_history = H_FALSE;
4064         return;
4065     }
4066
4067    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4068                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4069      /* [HGM] We seem to have switched variant unexpectedly
4070       * Try to guess new variant from board size
4071       */
4072           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4073           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4074           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4075           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4076           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4077           if(!weird) newVariant = VariantNormal;
4078           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4079           /* Get a move list just to see the header, which
4080              will tell us whether this is really bug or zh */
4081           if (ics_getting_history == H_FALSE) {
4082             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4083             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4084             SendToICS(str);
4085           }
4086     }
4087
4088     /* Take action if this is the first board of a new game, or of a
4089        different game than is currently being displayed.  */
4090     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4091         relation == RELATION_ISOLATED_BOARD) {
4092
4093         /* Forget the old game and get the history (if any) of the new one */
4094         if (gameMode != BeginningOfGame) {
4095           Reset(TRUE, TRUE);
4096         }
4097         newGame = TRUE;
4098         if (appData.autoRaiseBoard) BoardToTop();
4099         prevMove = -3;
4100         if (gamenum == -1) {
4101             newGameMode = IcsIdle;
4102         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4103                    appData.getMoveList && !reqFlag) {
4104             /* Need to get game history */
4105             ics_getting_history = H_REQUESTED;
4106             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4107             SendToICS(str);
4108         }
4109
4110         /* Initially flip the board to have black on the bottom if playing
4111            black or if the ICS flip flag is set, but let the user change
4112            it with the Flip View button. */
4113         flipView = appData.autoFlipView ?
4114           (newGameMode == IcsPlayingBlack) || ics_flip :
4115           appData.flipView;
4116
4117         /* Done with values from previous mode; copy in new ones */
4118         gameMode = newGameMode;
4119         ModeHighlight();
4120         ics_gamenum = gamenum;
4121         if (gamenum == gs_gamenum) {
4122             int klen = strlen(gs_kind);
4123             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4124             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4125             gameInfo.event = StrSave(str);
4126         } else {
4127             gameInfo.event = StrSave("ICS game");
4128         }
4129         gameInfo.site = StrSave(appData.icsHost);
4130         gameInfo.date = PGNDate();
4131         gameInfo.round = StrSave("-");
4132         gameInfo.white = StrSave(white);
4133         gameInfo.black = StrSave(black);
4134         timeControl = basetime * 60 * 1000;
4135         timeControl_2 = 0;
4136         timeIncrement = increment * 1000;
4137         movesPerSession = 0;
4138         gameInfo.timeControl = TimeControlTagValue();
4139         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4140   if (appData.debugMode) {
4141     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4142     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4143     setbuf(debugFP, NULL);
4144   }
4145
4146         gameInfo.outOfBook = NULL;
4147
4148         /* Do we have the ratings? */
4149         if (strcmp(player1Name, white) == 0 &&
4150             strcmp(player2Name, black) == 0) {
4151             if (appData.debugMode)
4152               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4153                       player1Rating, player2Rating);
4154             gameInfo.whiteRating = player1Rating;
4155             gameInfo.blackRating = player2Rating;
4156         } else if (strcmp(player2Name, white) == 0 &&
4157                    strcmp(player1Name, black) == 0) {
4158             if (appData.debugMode)
4159               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4160                       player2Rating, player1Rating);
4161             gameInfo.whiteRating = player2Rating;
4162             gameInfo.blackRating = player1Rating;
4163         }
4164         player1Name[0] = player2Name[0] = NULLCHAR;
4165
4166         /* Silence shouts if requested */
4167         if (appData.quietPlay &&
4168             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4169             SendToICS(ics_prefix);
4170             SendToICS("set shout 0\n");
4171         }
4172     }
4173
4174     /* Deal with midgame name changes */
4175     if (!newGame) {
4176         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4177             if (gameInfo.white) free(gameInfo.white);
4178             gameInfo.white = StrSave(white);
4179         }
4180         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4181             if (gameInfo.black) free(gameInfo.black);
4182             gameInfo.black = StrSave(black);
4183         }
4184     }
4185
4186     /* Throw away game result if anything actually changes in examine mode */
4187     if (gameMode == IcsExamining && !newGame) {
4188         gameInfo.result = GameUnfinished;
4189         if (gameInfo.resultDetails != NULL) {
4190             free(gameInfo.resultDetails);
4191             gameInfo.resultDetails = NULL;
4192         }
4193     }
4194
4195     /* In pausing && IcsExamining mode, we ignore boards coming
4196        in if they are in a different variation than we are. */
4197     if (pauseExamInvalid) return;
4198     if (pausing && gameMode == IcsExamining) {
4199         if (moveNum <= pauseExamForwardMostMove) {
4200             pauseExamInvalid = TRUE;
4201             forwardMostMove = pauseExamForwardMostMove;
4202             return;
4203         }
4204     }
4205
4206   if (appData.debugMode) {
4207     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4208   }
4209     /* Parse the board */
4210     for (k = 0; k < ranks; k++) {
4211       for (j = 0; j < files; j++)
4212         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4213       if(gameInfo.holdingsWidth > 1) {
4214            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4215            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4216       }
4217     }
4218     CopyBoard(boards[moveNum], board);
4219     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4220     if (moveNum == 0) {
4221         startedFromSetupPosition =
4222           !CompareBoards(board, initialPosition);
4223         if(startedFromSetupPosition)
4224             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4225     }
4226
4227     /* [HGM] Set castling rights. Take the outermost Rooks,
4228        to make it also work for FRC opening positions. Note that board12
4229        is really defective for later FRC positions, as it has no way to
4230        indicate which Rook can castle if they are on the same side of King.
4231        For the initial position we grant rights to the outermost Rooks,
4232        and remember thos rights, and we then copy them on positions
4233        later in an FRC game. This means WB might not recognize castlings with
4234        Rooks that have moved back to their original position as illegal,
4235        but in ICS mode that is not its job anyway.
4236     */
4237     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4238     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4239
4240         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4241             if(board[0][i] == WhiteRook) j = i;
4242         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4243         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4244             if(board[0][i] == WhiteRook) j = i;
4245         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4246         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4247             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4248         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4249         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4250             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4251         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4252
4253         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4254         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4255             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4256         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4257             if(board[BOARD_HEIGHT-1][k] == bKing)
4258                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4259         if(gameInfo.variant == VariantTwoKings) {
4260             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4261             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4262             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4263         }
4264     } else { int r;
4265         r = boards[moveNum][CASTLING][0] = initialRights[0];
4266         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4267         r = boards[moveNum][CASTLING][1] = initialRights[1];
4268         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4269         r = boards[moveNum][CASTLING][3] = initialRights[3];
4270         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4271         r = boards[moveNum][CASTLING][4] = initialRights[4];
4272         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4273         /* wildcastle kludge: always assume King has rights */
4274         r = boards[moveNum][CASTLING][2] = initialRights[2];
4275         r = boards[moveNum][CASTLING][5] = initialRights[5];
4276     }
4277     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4278     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4279
4280
4281     if (ics_getting_history == H_GOT_REQ_HEADER ||
4282         ics_getting_history == H_GOT_UNREQ_HEADER) {
4283         /* This was an initial position from a move list, not
4284            the current position */
4285         return;
4286     }
4287
4288     /* Update currentMove and known move number limits */
4289     newMove = newGame || moveNum > forwardMostMove;
4290
4291     if (newGame) {
4292         forwardMostMove = backwardMostMove = currentMove = moveNum;
4293         if (gameMode == IcsExamining && moveNum == 0) {
4294           /* Workaround for ICS limitation: we are not told the wild
4295              type when starting to examine a game.  But if we ask for
4296              the move list, the move list header will tell us */
4297             ics_getting_history = H_REQUESTED;
4298             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4299             SendToICS(str);
4300         }
4301     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4302                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4303 #if ZIPPY
4304         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4305         /* [HGM] applied this also to an engine that is silently watching        */
4306         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4307             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4308             gameInfo.variant == currentlyInitializedVariant) {
4309           takeback = forwardMostMove - moveNum;
4310           for (i = 0; i < takeback; i++) {
4311             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4312             SendToProgram("undo\n", &first);
4313           }
4314         }
4315 #endif
4316
4317         forwardMostMove = moveNum;
4318         if (!pausing || currentMove > forwardMostMove)
4319           currentMove = forwardMostMove;
4320     } else {
4321         /* New part of history that is not contiguous with old part */
4322         if (pausing && gameMode == IcsExamining) {
4323             pauseExamInvalid = TRUE;
4324             forwardMostMove = pauseExamForwardMostMove;
4325             return;
4326         }
4327         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4328 #if ZIPPY
4329             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4330                 // [HGM] when we will receive the move list we now request, it will be
4331                 // fed to the engine from the first move on. So if the engine is not
4332                 // in the initial position now, bring it there.
4333                 InitChessProgram(&first, 0);
4334             }
4335 #endif
4336             ics_getting_history = H_REQUESTED;
4337             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4338             SendToICS(str);
4339         }
4340         forwardMostMove = backwardMostMove = currentMove = moveNum;
4341     }
4342
4343     /* Update the clocks */
4344     if (strchr(elapsed_time, '.')) {
4345       /* Time is in ms */
4346       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4347       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4348     } else {
4349       /* Time is in seconds */
4350       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4351       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4352     }
4353
4354
4355 #if ZIPPY
4356     if (appData.zippyPlay && newGame &&
4357         gameMode != IcsObserving && gameMode != IcsIdle &&
4358         gameMode != IcsExamining)
4359       ZippyFirstBoard(moveNum, basetime, increment);
4360 #endif
4361
4362     /* Put the move on the move list, first converting
4363        to canonical algebraic form. */
4364     if (moveNum > 0) {
4365   if (appData.debugMode) {
4366     if (appData.debugMode) { int f = forwardMostMove;
4367         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4368                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4369                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4370     }
4371     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4372     fprintf(debugFP, "moveNum = %d\n", moveNum);
4373     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4374     setbuf(debugFP, NULL);
4375   }
4376         if (moveNum <= backwardMostMove) {
4377             /* We don't know what the board looked like before
4378                this move.  Punt. */
4379           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4380             strcat(parseList[moveNum - 1], " ");
4381             strcat(parseList[moveNum - 1], elapsed_time);
4382             moveList[moveNum - 1][0] = NULLCHAR;
4383         } else if (strcmp(move_str, "none") == 0) {
4384             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4385             /* Again, we don't know what the board looked like;
4386                this is really the start of the game. */
4387             parseList[moveNum - 1][0] = NULLCHAR;
4388             moveList[moveNum - 1][0] = NULLCHAR;
4389             backwardMostMove = moveNum;
4390             startedFromSetupPosition = TRUE;
4391             fromX = fromY = toX = toY = -1;
4392         } else {
4393           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4394           //                 So we parse the long-algebraic move string in stead of the SAN move
4395           int valid; char buf[MSG_SIZ], *prom;
4396
4397           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4398                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4399           // str looks something like "Q/a1-a2"; kill the slash
4400           if(str[1] == '/')
4401             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4402           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4403           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4404                 strcat(buf, prom); // long move lacks promo specification!
4405           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4406                 if(appData.debugMode)
4407                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4408                 safeStrCpy(move_str, buf, MSG_SIZ);
4409           }
4410           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4411                                 &fromX, &fromY, &toX, &toY, &promoChar)
4412                || ParseOneMove(buf, moveNum - 1, &moveType,
4413                                 &fromX, &fromY, &toX, &toY, &promoChar);
4414           // end of long SAN patch
4415           if (valid) {
4416             (void) CoordsToAlgebraic(boards[moveNum - 1],
4417                                      PosFlags(moveNum - 1),
4418                                      fromY, fromX, toY, toX, promoChar,
4419                                      parseList[moveNum-1]);
4420             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4421               case MT_NONE:
4422               case MT_STALEMATE:
4423               default:
4424                 break;
4425               case MT_CHECK:
4426                 if(gameInfo.variant != VariantShogi)
4427                     strcat(parseList[moveNum - 1], "+");
4428                 break;
4429               case MT_CHECKMATE:
4430               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4431                 strcat(parseList[moveNum - 1], "#");
4432                 break;
4433             }
4434             strcat(parseList[moveNum - 1], " ");
4435             strcat(parseList[moveNum - 1], elapsed_time);
4436             /* currentMoveString is set as a side-effect of ParseOneMove */
4437             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4438             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4439             strcat(moveList[moveNum - 1], "\n");
4440
4441             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4442                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4443               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4444                 ChessSquare old, new = boards[moveNum][k][j];
4445                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4446                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4447                   if(old == new) continue;
4448                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4449                   else if(new == WhiteWazir || new == BlackWazir) {
4450                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4451                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4452                       else boards[moveNum][k][j] = old; // preserve type of Gold
4453                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4454                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4455               }
4456           } else {
4457             /* Move from ICS was illegal!?  Punt. */
4458             if (appData.debugMode) {
4459               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4460               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4461             }
4462             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4463             strcat(parseList[moveNum - 1], " ");
4464             strcat(parseList[moveNum - 1], elapsed_time);
4465             moveList[moveNum - 1][0] = NULLCHAR;
4466             fromX = fromY = toX = toY = -1;
4467           }
4468         }
4469   if (appData.debugMode) {
4470     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4471     setbuf(debugFP, NULL);
4472   }
4473
4474 #if ZIPPY
4475         /* Send move to chess program (BEFORE animating it). */
4476         if (appData.zippyPlay && !newGame && newMove &&
4477            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4478
4479             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4480                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4481                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4482                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4483                             move_str);
4484                     DisplayError(str, 0);
4485                 } else {
4486                     if (first.sendTime) {
4487                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4488                     }
4489                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4490                     if (firstMove && !bookHit) {
4491                         firstMove = FALSE;
4492                         if (first.useColors) {
4493                           SendToProgram(gameMode == IcsPlayingWhite ?
4494                                         "white\ngo\n" :
4495                                         "black\ngo\n", &first);
4496                         } else {
4497                           SendToProgram("go\n", &first);
4498                         }
4499                         first.maybeThinking = TRUE;
4500                     }
4501                 }
4502             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4503               if (moveList[moveNum - 1][0] == NULLCHAR) {
4504                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4505                 DisplayError(str, 0);
4506               } else {
4507                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4508                 SendMoveToProgram(moveNum - 1, &first);
4509               }
4510             }
4511         }
4512 #endif
4513     }
4514
4515     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4516         /* If move comes from a remote source, animate it.  If it
4517            isn't remote, it will have already been animated. */
4518         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4519             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4520         }
4521         if (!pausing && appData.highlightLastMove) {
4522             SetHighlights(fromX, fromY, toX, toY);
4523         }
4524     }
4525
4526     /* Start the clocks */
4527     whiteFlag = blackFlag = FALSE;
4528     appData.clockMode = !(basetime == 0 && increment == 0);
4529     if (ticking == 0) {
4530       ics_clock_paused = TRUE;
4531       StopClocks();
4532     } else if (ticking == 1) {
4533       ics_clock_paused = FALSE;
4534     }
4535     if (gameMode == IcsIdle ||
4536         relation == RELATION_OBSERVING_STATIC ||
4537         relation == RELATION_EXAMINING ||
4538         ics_clock_paused)
4539       DisplayBothClocks();
4540     else
4541       StartClocks();
4542
4543     /* Display opponents and material strengths */
4544     if (gameInfo.variant != VariantBughouse &&
4545         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4546         if (tinyLayout || smallLayout) {
4547             if(gameInfo.variant == VariantNormal)
4548               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4549                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4550                     basetime, increment);
4551             else
4552               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4553                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4554                     basetime, increment, (int) gameInfo.variant);
4555         } else {
4556             if(gameInfo.variant == VariantNormal)
4557               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4558                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4559                     basetime, increment);
4560             else
4561               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4562                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4563                     basetime, increment, VariantName(gameInfo.variant));
4564         }
4565         DisplayTitle(str);
4566   if (appData.debugMode) {
4567     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4568   }
4569     }
4570
4571
4572     /* Display the board */
4573     if (!pausing && !appData.noGUI) {
4574
4575       if (appData.premove)
4576           if (!gotPremove ||
4577              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4578              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4579               ClearPremoveHighlights();
4580
4581       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4582         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4583       DrawPosition(j, boards[currentMove]);
4584
4585       DisplayMove(moveNum - 1);
4586       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4587             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4588               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4589         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4590       }
4591     }
4592
4593     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4594 #if ZIPPY
4595     if(bookHit) { // [HGM] book: simulate book reply
4596         static char bookMove[MSG_SIZ]; // a bit generous?
4597
4598         programStats.nodes = programStats.depth = programStats.time =
4599         programStats.score = programStats.got_only_move = 0;
4600         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4601
4602         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4603         strcat(bookMove, bookHit);
4604         HandleMachineMove(bookMove, &first);
4605     }
4606 #endif
4607 }
4608
4609 void
4610 GetMoveListEvent()
4611 {
4612     char buf[MSG_SIZ];
4613     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4614         ics_getting_history = H_REQUESTED;
4615         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4616         SendToICS(buf);
4617     }
4618 }
4619
4620 void
4621 AnalysisPeriodicEvent(force)
4622      int force;
4623 {
4624     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4625          && !force) || !appData.periodicUpdates)
4626       return;
4627
4628     /* Send . command to Crafty to collect stats */
4629     SendToProgram(".\n", &first);
4630
4631     /* Don't send another until we get a response (this makes
4632        us stop sending to old Crafty's which don't understand
4633        the "." command (sending illegal cmds resets node count & time,
4634        which looks bad)) */
4635     programStats.ok_to_send = 0;
4636 }
4637
4638 void ics_update_width(new_width)
4639         int new_width;
4640 {
4641         ics_printf("set width %d\n", new_width);
4642 }
4643
4644 void
4645 SendMoveToProgram(moveNum, cps)
4646      int moveNum;
4647      ChessProgramState *cps;
4648 {
4649     char buf[MSG_SIZ];
4650
4651     if (cps->useUsermove) {
4652       SendToProgram("usermove ", cps);
4653     }
4654     if (cps->useSAN) {
4655       char *space;
4656       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4657         int len = space - parseList[moveNum];
4658         memcpy(buf, parseList[moveNum], len);
4659         buf[len++] = '\n';
4660         buf[len] = NULLCHAR;
4661       } else {
4662         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4663       }
4664       SendToProgram(buf, cps);
4665     } else {
4666       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4667         AlphaRank(moveList[moveNum], 4);
4668         SendToProgram(moveList[moveNum], cps);
4669         AlphaRank(moveList[moveNum], 4); // and back
4670       } else
4671       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4672        * the engine. It would be nice to have a better way to identify castle
4673        * moves here. */
4674       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4675                                                                          && cps->useOOCastle) {
4676         int fromX = moveList[moveNum][0] - AAA;
4677         int fromY = moveList[moveNum][1] - ONE;
4678         int toX = moveList[moveNum][2] - AAA;
4679         int toY = moveList[moveNum][3] - ONE;
4680         if((boards[moveNum][fromY][fromX] == WhiteKing
4681             && boards[moveNum][toY][toX] == WhiteRook)
4682            || (boards[moveNum][fromY][fromX] == BlackKing
4683                && boards[moveNum][toY][toX] == BlackRook)) {
4684           if(toX > fromX) SendToProgram("O-O\n", cps);
4685           else SendToProgram("O-O-O\n", cps);
4686         }
4687         else SendToProgram(moveList[moveNum], cps);
4688       }
4689       else SendToProgram(moveList[moveNum], cps);
4690       /* End of additions by Tord */
4691     }
4692
4693     /* [HGM] setting up the opening has brought engine in force mode! */
4694     /*       Send 'go' if we are in a mode where machine should play. */
4695     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4696         (gameMode == TwoMachinesPlay   ||
4697 #if ZIPPY
4698          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4699 #endif
4700          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4701         SendToProgram("go\n", cps);
4702   if (appData.debugMode) {
4703     fprintf(debugFP, "(extra)\n");
4704   }
4705     }
4706     setboardSpoiledMachineBlack = 0;
4707 }
4708
4709 void
4710 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4711      ChessMove moveType;
4712      int fromX, fromY, toX, toY;
4713      char promoChar;
4714 {
4715     char user_move[MSG_SIZ];
4716
4717     switch (moveType) {
4718       default:
4719         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4720                 (int)moveType, fromX, fromY, toX, toY);
4721         DisplayError(user_move + strlen("say "), 0);
4722         break;
4723       case WhiteKingSideCastle:
4724       case BlackKingSideCastle:
4725       case WhiteQueenSideCastleWild:
4726       case BlackQueenSideCastleWild:
4727       /* PUSH Fabien */
4728       case WhiteHSideCastleFR:
4729       case BlackHSideCastleFR:
4730       /* POP Fabien */
4731         snprintf(user_move, MSG_SIZ, "o-o\n");
4732         break;
4733       case WhiteQueenSideCastle:
4734       case BlackQueenSideCastle:
4735       case WhiteKingSideCastleWild:
4736       case BlackKingSideCastleWild:
4737       /* PUSH Fabien */
4738       case WhiteASideCastleFR:
4739       case BlackASideCastleFR:
4740       /* POP Fabien */
4741         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4742         break;
4743       case WhiteNonPromotion:
4744       case BlackNonPromotion:
4745         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4746         break;
4747       case WhitePromotion:
4748       case BlackPromotion:
4749         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4750           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4751                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4752                 PieceToChar(WhiteFerz));
4753         else if(gameInfo.variant == VariantGreat)
4754           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4755                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4756                 PieceToChar(WhiteMan));
4757         else
4758           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4759                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4760                 promoChar);
4761         break;
4762       case WhiteDrop:
4763       case BlackDrop:
4764       drop:
4765         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4766                  ToUpper(PieceToChar((ChessSquare) fromX)),
4767                  AAA + toX, ONE + toY);
4768         break;
4769       case IllegalMove:  /* could be a variant we don't quite understand */
4770         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4771       case NormalMove:
4772       case WhiteCapturesEnPassant:
4773       case BlackCapturesEnPassant:
4774         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4775                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4776         break;
4777     }
4778     SendToICS(user_move);
4779     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4780         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4781 }
4782
4783 void
4784 UploadGameEvent()
4785 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4786     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4787     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4788     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4789         DisplayError("You cannot do this while you are playing or observing", 0);
4790         return;
4791     }
4792     if(gameMode != IcsExamining) { // is this ever not the case?
4793         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4794
4795         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4796           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4797         } else { // on FICS we must first go to general examine mode
4798           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4799         }
4800         if(gameInfo.variant != VariantNormal) {
4801             // try figure out wild number, as xboard names are not always valid on ICS
4802             for(i=1; i<=36; i++) {
4803               snprintf(buf, MSG_SIZ, "wild/%d", i);
4804                 if(StringToVariant(buf) == gameInfo.variant) break;
4805             }
4806             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4807             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4808             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4809         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4810         SendToICS(ics_prefix);
4811         SendToICS(buf);
4812         if(startedFromSetupPosition || backwardMostMove != 0) {
4813           fen = PositionToFEN(backwardMostMove, NULL);
4814           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4815             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4816             SendToICS(buf);
4817           } else { // FICS: everything has to set by separate bsetup commands
4818             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4819             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4820             SendToICS(buf);
4821             if(!WhiteOnMove(backwardMostMove)) {
4822                 SendToICS("bsetup tomove black\n");
4823             }
4824             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4825             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4826             SendToICS(buf);
4827             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4828             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4829             SendToICS(buf);
4830             i = boards[backwardMostMove][EP_STATUS];
4831             if(i >= 0) { // set e.p.
4832               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4833                 SendToICS(buf);
4834             }
4835             bsetup++;
4836           }
4837         }
4838       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4839             SendToICS("bsetup done\n"); // switch to normal examining.
4840     }
4841     for(i = backwardMostMove; i<last; i++) {
4842         char buf[20];
4843         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4844         SendToICS(buf);
4845     }
4846     SendToICS(ics_prefix);
4847     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4848 }
4849
4850 void
4851 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4852      int rf, ff, rt, ft;
4853      char promoChar;
4854      char move[7];
4855 {
4856     if (rf == DROP_RANK) {
4857       sprintf(move, "%c@%c%c\n",
4858                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4859     } else {
4860         if (promoChar == 'x' || promoChar == NULLCHAR) {
4861           sprintf(move, "%c%c%c%c\n",
4862                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4863         } else {
4864             sprintf(move, "%c%c%c%c%c\n",
4865                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4866         }
4867     }
4868 }
4869
4870 void
4871 ProcessICSInitScript(f)
4872      FILE *f;
4873 {
4874     char buf[MSG_SIZ];
4875
4876     while (fgets(buf, MSG_SIZ, f)) {
4877         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4878     }
4879
4880     fclose(f);
4881 }
4882
4883
4884 static int lastX, lastY, selectFlag, dragging;
4885
4886 void
4887 Sweep(int step)
4888 {
4889     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4890     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4891     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4892     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4893     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4894     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4895     do {
4896         promoSweep -= step;
4897         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4898         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4899         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4900         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4901         if(!step) step = 1;
4902     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4903             appData.testLegality && (promoSweep == king ||
4904             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4905     ChangeDragPiece(promoSweep);
4906 }
4907
4908 int PromoScroll(int x, int y)
4909 {
4910   int step = 0;
4911
4912   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4913   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4914   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4915   if(!step) return FALSE;
4916   lastX = x; lastY = y;
4917   if((promoSweep < BlackPawn) == flipView) step = -step;
4918   if(step > 0) selectFlag = 1;
4919   if(!selectFlag) Sweep(step);
4920   return FALSE;
4921 }
4922
4923 void
4924 NextPiece(int step)
4925 {
4926     ChessSquare piece = boards[currentMove][toY][toX];
4927     do {
4928         pieceSweep -= step;
4929         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4930         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4931         if(!step) step = -1;
4932     } while(PieceToChar(pieceSweep) == '.');
4933     boards[currentMove][toY][toX] = pieceSweep;
4934     DrawPosition(FALSE, boards[currentMove]);
4935     boards[currentMove][toY][toX] = piece;
4936 }
4937 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4938 void
4939 AlphaRank(char *move, int n)
4940 {
4941 //    char *p = move, c; int x, y;
4942
4943     if (appData.debugMode) {
4944         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4945     }
4946
4947     if(move[1]=='*' &&
4948        move[2]>='0' && move[2]<='9' &&
4949        move[3]>='a' && move[3]<='x'    ) {
4950         move[1] = '@';
4951         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4952         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4953     } else
4954     if(move[0]>='0' && move[0]<='9' &&
4955        move[1]>='a' && move[1]<='x' &&
4956        move[2]>='0' && move[2]<='9' &&
4957        move[3]>='a' && move[3]<='x'    ) {
4958         /* input move, Shogi -> normal */
4959         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4960         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4961         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4962         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4963     } else
4964     if(move[1]=='@' &&
4965        move[3]>='0' && move[3]<='9' &&
4966        move[2]>='a' && move[2]<='x'    ) {
4967         move[1] = '*';
4968         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4969         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4970     } else
4971     if(
4972        move[0]>='a' && move[0]<='x' &&
4973        move[3]>='0' && move[3]<='9' &&
4974        move[2]>='a' && move[2]<='x'    ) {
4975          /* output move, normal -> Shogi */
4976         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4977         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4978         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4979         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4980         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4981     }
4982     if (appData.debugMode) {
4983         fprintf(debugFP, "   out = '%s'\n", move);
4984     }
4985 }
4986
4987 char yy_textstr[8000];
4988
4989 /* Parser for moves from gnuchess, ICS, or user typein box */
4990 Boolean
4991 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4992      char *move;
4993      int moveNum;
4994      ChessMove *moveType;
4995      int *fromX, *fromY, *toX, *toY;
4996      char *promoChar;
4997 {
4998     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4999
5000     switch (*moveType) {
5001       case WhitePromotion:
5002       case BlackPromotion:
5003       case WhiteNonPromotion:
5004       case BlackNonPromotion:
5005       case NormalMove:
5006       case WhiteCapturesEnPassant:
5007       case BlackCapturesEnPassant:
5008       case WhiteKingSideCastle:
5009       case WhiteQueenSideCastle:
5010       case BlackKingSideCastle:
5011       case BlackQueenSideCastle:
5012       case WhiteKingSideCastleWild:
5013       case WhiteQueenSideCastleWild:
5014       case BlackKingSideCastleWild:
5015       case BlackQueenSideCastleWild:
5016       /* Code added by Tord: */
5017       case WhiteHSideCastleFR:
5018       case WhiteASideCastleFR:
5019       case BlackHSideCastleFR:
5020       case BlackASideCastleFR:
5021       /* End of code added by Tord */
5022       case IllegalMove:         /* bug or odd chess variant */
5023         *fromX = currentMoveString[0] - AAA;
5024         *fromY = currentMoveString[1] - ONE;
5025         *toX = currentMoveString[2] - AAA;
5026         *toY = currentMoveString[3] - ONE;
5027         *promoChar = currentMoveString[4];
5028         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5029             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5030     if (appData.debugMode) {
5031         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5032     }
5033             *fromX = *fromY = *toX = *toY = 0;
5034             return FALSE;
5035         }
5036         if (appData.testLegality) {
5037           return (*moveType != IllegalMove);
5038         } else {
5039           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5040                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5041         }
5042
5043       case WhiteDrop:
5044       case BlackDrop:
5045         *fromX = *moveType == WhiteDrop ?
5046           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5047           (int) CharToPiece(ToLower(currentMoveString[0]));
5048         *fromY = DROP_RANK;
5049         *toX = currentMoveString[2] - AAA;
5050         *toY = currentMoveString[3] - ONE;
5051         *promoChar = NULLCHAR;
5052         return TRUE;
5053
5054       case AmbiguousMove:
5055       case ImpossibleMove:
5056       case EndOfFile:
5057       case ElapsedTime:
5058       case Comment:
5059       case PGNTag:
5060       case NAG:
5061       case WhiteWins:
5062       case BlackWins:
5063       case GameIsDrawn:
5064       default:
5065     if (appData.debugMode) {
5066         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5067     }
5068         /* bug? */
5069         *fromX = *fromY = *toX = *toY = 0;
5070         *promoChar = NULLCHAR;
5071         return FALSE;
5072     }
5073 }
5074
5075
5076 void
5077 ParsePV(char *pv, Boolean storeComments)
5078 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5079   int fromX, fromY, toX, toY; char promoChar;
5080   ChessMove moveType;
5081   Boolean valid;
5082   int nr = 0;
5083
5084   endPV = forwardMostMove;
5085   do {
5086     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5087     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5088     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5089 if(appData.debugMode){
5090 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);
5091 }
5092     if(!valid && nr == 0 &&
5093        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5094         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5095         // Hande case where played move is different from leading PV move
5096         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5097         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5098         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5099         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5100           endPV += 2; // if position different, keep this
5101           moveList[endPV-1][0] = fromX + AAA;
5102           moveList[endPV-1][1] = fromY + ONE;
5103           moveList[endPV-1][2] = toX + AAA;
5104           moveList[endPV-1][3] = toY + ONE;
5105           parseList[endPV-1][0] = NULLCHAR;
5106           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5107         }
5108       }
5109     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5110     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5111     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5112     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5113         valid++; // allow comments in PV
5114         continue;
5115     }
5116     nr++;
5117     if(endPV+1 > framePtr) break; // no space, truncate
5118     if(!valid) break;
5119     endPV++;
5120     CopyBoard(boards[endPV], boards[endPV-1]);
5121     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5122     moveList[endPV-1][0] = fromX + AAA;
5123     moveList[endPV-1][1] = fromY + ONE;
5124     moveList[endPV-1][2] = toX + AAA;
5125     moveList[endPV-1][3] = toY + ONE;
5126     moveList[endPV-1][4] = promoChar;
5127     moveList[endPV-1][5] = NULLCHAR;
5128     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5129     if(storeComments)
5130         CoordsToAlgebraic(boards[endPV - 1],
5131                              PosFlags(endPV - 1),
5132                              fromY, fromX, toY, toX, promoChar,
5133                              parseList[endPV - 1]);
5134     else
5135         parseList[endPV-1][0] = NULLCHAR;
5136   } while(valid);
5137   currentMove = endPV;
5138   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5139   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5140                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5141   DrawPosition(TRUE, boards[currentMove]);
5142 }
5143
5144 Boolean
5145 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5146 {
5147         int startPV;
5148         char *p;
5149
5150         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5151         lastX = x; lastY = y;
5152         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5153         startPV = index;
5154         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5155         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5156         index = startPV;
5157         do{ while(buf[index] && buf[index] != '\n') index++;
5158         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5159         buf[index] = 0;
5160         ParsePV(buf+startPV, FALSE);
5161         *start = startPV; *end = index-1;
5162         return TRUE;
5163 }
5164
5165 Boolean
5166 LoadPV(int x, int y)
5167 { // called on right mouse click to load PV
5168   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5169   lastX = x; lastY = y;
5170   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5171   return TRUE;
5172 }
5173
5174 void
5175 UnLoadPV()
5176 {
5177   if(endPV < 0) return;
5178   endPV = -1;
5179   currentMove = forwardMostMove;
5180   ClearPremoveHighlights();
5181   DrawPosition(TRUE, boards[currentMove]);
5182 }
5183
5184 void
5185 MovePV(int x, int y, int h)
5186 { // step through PV based on mouse coordinates (called on mouse move)
5187   int margin = h>>3, step = 0;
5188
5189   // we must somehow check if right button is still down (might be released off board!)
5190   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5191   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5192   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5193   if(!step) return;
5194   lastX = x; lastY = y;
5195
5196   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5197   if(endPV < 0) return;
5198   if(y < margin) step = 1; else
5199   if(y > h - margin) step = -1;
5200   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5201   currentMove += step;
5202   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5203   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5204                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5205   DrawPosition(FALSE, boards[currentMove]);
5206 }
5207
5208
5209 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5210 // All positions will have equal probability, but the current method will not provide a unique
5211 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5212 #define DARK 1
5213 #define LITE 2
5214 #define ANY 3
5215
5216 int squaresLeft[4];
5217 int piecesLeft[(int)BlackPawn];
5218 int seed, nrOfShuffles;
5219
5220 void GetPositionNumber()
5221 {       // sets global variable seed
5222         int i;
5223
5224         seed = appData.defaultFrcPosition;
5225         if(seed < 0) { // randomize based on time for negative FRC position numbers
5226                 for(i=0; i<50; i++) seed += random();
5227                 seed = random() ^ random() >> 8 ^ random() << 8;
5228                 if(seed<0) seed = -seed;
5229         }
5230 }
5231
5232 int put(Board board, int pieceType, int rank, int n, int shade)
5233 // put the piece on the (n-1)-th empty squares of the given shade
5234 {
5235         int i;
5236
5237         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5238                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5239                         board[rank][i] = (ChessSquare) pieceType;
5240                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5241                         squaresLeft[ANY]--;
5242                         piecesLeft[pieceType]--;
5243                         return i;
5244                 }
5245         }
5246         return -1;
5247 }
5248
5249
5250 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5251 // calculate where the next piece goes, (any empty square), and put it there
5252 {
5253         int i;
5254
5255         i = seed % squaresLeft[shade];
5256         nrOfShuffles *= squaresLeft[shade];
5257         seed /= squaresLeft[shade];
5258         put(board, pieceType, rank, i, shade);
5259 }
5260
5261 void AddTwoPieces(Board board, int pieceType, int rank)
5262 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5263 {
5264         int i, n=squaresLeft[ANY], j=n-1, k;
5265
5266         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5267         i = seed % k;  // pick one
5268         nrOfShuffles *= k;
5269         seed /= k;
5270         while(i >= j) i -= j--;
5271         j = n - 1 - j; i += j;
5272         put(board, pieceType, rank, j, ANY);
5273         put(board, pieceType, rank, i, ANY);
5274 }
5275
5276 void SetUpShuffle(Board board, int number)
5277 {
5278         int i, p, first=1;
5279
5280         GetPositionNumber(); nrOfShuffles = 1;
5281
5282         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5283         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5284         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5285
5286         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5287
5288         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5289             p = (int) board[0][i];
5290             if(p < (int) BlackPawn) piecesLeft[p] ++;
5291             board[0][i] = EmptySquare;
5292         }
5293
5294         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5295             // shuffles restricted to allow normal castling put KRR first
5296             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5297                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5298             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5299                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5300             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5301                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5302             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5303                 put(board, WhiteRook, 0, 0, ANY);
5304             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5305         }
5306
5307         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5308             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5309             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5310                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5311                 while(piecesLeft[p] >= 2) {
5312                     AddOnePiece(board, p, 0, LITE);
5313                     AddOnePiece(board, p, 0, DARK);
5314                 }
5315                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5316             }
5317
5318         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5319             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5320             // but we leave King and Rooks for last, to possibly obey FRC restriction
5321             if(p == (int)WhiteRook) continue;
5322             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5323             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5324         }
5325
5326         // now everything is placed, except perhaps King (Unicorn) and Rooks
5327
5328         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5329             // Last King gets castling rights
5330             while(piecesLeft[(int)WhiteUnicorn]) {
5331                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5332                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5333             }
5334
5335             while(piecesLeft[(int)WhiteKing]) {
5336                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5337                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5338             }
5339
5340
5341         } else {
5342             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5343             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5344         }
5345
5346         // Only Rooks can be left; simply place them all
5347         while(piecesLeft[(int)WhiteRook]) {
5348                 i = put(board, WhiteRook, 0, 0, ANY);
5349                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5350                         if(first) {
5351                                 first=0;
5352                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5353                         }
5354                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5355                 }
5356         }
5357         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5358             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5359         }
5360
5361         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5362 }
5363
5364 int SetCharTable( char *table, const char * map )
5365 /* [HGM] moved here from winboard.c because of its general usefulness */
5366 /*       Basically a safe strcpy that uses the last character as King */
5367 {
5368     int result = FALSE; int NrPieces;
5369
5370     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5371                     && NrPieces >= 12 && !(NrPieces&1)) {
5372         int i; /* [HGM] Accept even length from 12 to 34 */
5373
5374         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5375         for( i=0; i<NrPieces/2-1; i++ ) {
5376             table[i] = map[i];
5377             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5378         }
5379         table[(int) WhiteKing]  = map[NrPieces/2-1];
5380         table[(int) BlackKing]  = map[NrPieces-1];
5381
5382         result = TRUE;
5383     }
5384
5385     return result;
5386 }
5387
5388 void Prelude(Board board)
5389 {       // [HGM] superchess: random selection of exo-pieces
5390         int i, j, k; ChessSquare p;
5391         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5392
5393         GetPositionNumber(); // use FRC position number
5394
5395         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5396             SetCharTable(pieceToChar, appData.pieceToCharTable);
5397             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5398                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5399         }
5400
5401         j = seed%4;                 seed /= 4;
5402         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5403         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5404         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5405         j = seed%3 + (seed%3 >= j); seed /= 3;
5406         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5407         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5408         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5409         j = seed%3;                 seed /= 3;
5410         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5411         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5412         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5413         j = seed%2 + (seed%2 >= j); seed /= 2;
5414         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5415         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5416         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5417         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5418         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5419         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5420         put(board, exoPieces[0],    0, 0, ANY);
5421         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5422 }
5423
5424 void
5425 InitPosition(redraw)
5426      int redraw;
5427 {
5428     ChessSquare (* pieces)[BOARD_FILES];
5429     int i, j, pawnRow, overrule,
5430     oldx = gameInfo.boardWidth,
5431     oldy = gameInfo.boardHeight,
5432     oldh = gameInfo.holdingsWidth;
5433     static int oldv;
5434
5435     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5436
5437     /* [AS] Initialize pv info list [HGM] and game status */
5438     {
5439         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5440             pvInfoList[i].depth = 0;
5441             boards[i][EP_STATUS] = EP_NONE;
5442             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5443         }
5444
5445         initialRulePlies = 0; /* 50-move counter start */
5446
5447         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5448         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5449     }
5450
5451
5452     /* [HGM] logic here is completely changed. In stead of full positions */
5453     /* the initialized data only consist of the two backranks. The switch */
5454     /* selects which one we will use, which is than copied to the Board   */
5455     /* initialPosition, which for the rest is initialized by Pawns and    */
5456     /* empty squares. This initial position is then copied to boards[0],  */
5457     /* possibly after shuffling, so that it remains available.            */
5458
5459     gameInfo.holdingsWidth = 0; /* default board sizes */
5460     gameInfo.boardWidth    = 8;
5461     gameInfo.boardHeight   = 8;
5462     gameInfo.holdingsSize  = 0;
5463     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5464     for(i=0; i<BOARD_FILES-2; i++)
5465       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5466     initialPosition[EP_STATUS] = EP_NONE;
5467     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5468     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5469          SetCharTable(pieceNickName, appData.pieceNickNames);
5470     else SetCharTable(pieceNickName, "............");
5471     pieces = FIDEArray;
5472
5473     switch (gameInfo.variant) {
5474     case VariantFischeRandom:
5475       shuffleOpenings = TRUE;
5476     default:
5477       break;
5478     case VariantShatranj:
5479       pieces = ShatranjArray;
5480       nrCastlingRights = 0;
5481       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5482       break;
5483     case VariantMakruk:
5484       pieces = makrukArray;
5485       nrCastlingRights = 0;
5486       startedFromSetupPosition = TRUE;
5487       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5488       break;
5489     case VariantTwoKings:
5490       pieces = twoKingsArray;
5491       break;
5492     case VariantCapaRandom:
5493       shuffleOpenings = TRUE;
5494     case VariantCapablanca:
5495       pieces = CapablancaArray;
5496       gameInfo.boardWidth = 10;
5497       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5498       break;
5499     case VariantGothic:
5500       pieces = GothicArray;
5501       gameInfo.boardWidth = 10;
5502       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5503       break;
5504     case VariantSChess:
5505       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5506       gameInfo.holdingsSize = 7;
5507       break;
5508     case VariantJanus:
5509       pieces = JanusArray;
5510       gameInfo.boardWidth = 10;
5511       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5512       nrCastlingRights = 6;
5513         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5514         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5515         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5516         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5517         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5518         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5519       break;
5520     case VariantFalcon:
5521       pieces = FalconArray;
5522       gameInfo.boardWidth = 10;
5523       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5524       break;
5525     case VariantXiangqi:
5526       pieces = XiangqiArray;
5527       gameInfo.boardWidth  = 9;
5528       gameInfo.boardHeight = 10;
5529       nrCastlingRights = 0;
5530       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5531       break;
5532     case VariantShogi:
5533       pieces = ShogiArray;
5534       gameInfo.boardWidth  = 9;
5535       gameInfo.boardHeight = 9;
5536       gameInfo.holdingsSize = 7;
5537       nrCastlingRights = 0;
5538       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5539       break;
5540     case VariantCourier:
5541       pieces = CourierArray;
5542       gameInfo.boardWidth  = 12;
5543       nrCastlingRights = 0;
5544       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5545       break;
5546     case VariantKnightmate:
5547       pieces = KnightmateArray;
5548       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5549       break;
5550     case VariantSpartan:
5551       pieces = SpartanArray;
5552       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5553       break;
5554     case VariantFairy:
5555       pieces = fairyArray;
5556       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5557       break;
5558     case VariantGreat:
5559       pieces = GreatArray;
5560       gameInfo.boardWidth = 10;
5561       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5562       gameInfo.holdingsSize = 8;
5563       break;
5564     case VariantSuper:
5565       pieces = FIDEArray;
5566       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5567       gameInfo.holdingsSize = 8;
5568       startedFromSetupPosition = TRUE;
5569       break;
5570     case VariantCrazyhouse:
5571     case VariantBughouse:
5572       pieces = FIDEArray;
5573       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5574       gameInfo.holdingsSize = 5;
5575       break;
5576     case VariantWildCastle:
5577       pieces = FIDEArray;
5578       /* !!?shuffle with kings guaranteed to be on d or e file */
5579       shuffleOpenings = 1;
5580       break;
5581     case VariantNoCastle:
5582       pieces = FIDEArray;
5583       nrCastlingRights = 0;
5584       /* !!?unconstrained back-rank shuffle */
5585       shuffleOpenings = 1;
5586       break;
5587     }
5588
5589     overrule = 0;
5590     if(appData.NrFiles >= 0) {
5591         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5592         gameInfo.boardWidth = appData.NrFiles;
5593     }
5594     if(appData.NrRanks >= 0) {
5595         gameInfo.boardHeight = appData.NrRanks;
5596     }
5597     if(appData.holdingsSize >= 0) {
5598         i = appData.holdingsSize;
5599         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5600         gameInfo.holdingsSize = i;
5601     }
5602     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5603     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5604         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5605
5606     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5607     if(pawnRow < 1) pawnRow = 1;
5608     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5609
5610     /* User pieceToChar list overrules defaults */
5611     if(appData.pieceToCharTable != NULL)
5612         SetCharTable(pieceToChar, appData.pieceToCharTable);
5613
5614     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5615
5616         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5617             s = (ChessSquare) 0; /* account holding counts in guard band */
5618         for( i=0; i<BOARD_HEIGHT; i++ )
5619             initialPosition[i][j] = s;
5620
5621         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5622         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5623         initialPosition[pawnRow][j] = WhitePawn;
5624         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5625         if(gameInfo.variant == VariantXiangqi) {
5626             if(j&1) {
5627                 initialPosition[pawnRow][j] =
5628                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5629                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5630                    initialPosition[2][j] = WhiteCannon;
5631                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5632                 }
5633             }
5634         }
5635         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5636     }
5637     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5638
5639             j=BOARD_LEFT+1;
5640             initialPosition[1][j] = WhiteBishop;
5641             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5642             j=BOARD_RGHT-2;
5643             initialPosition[1][j] = WhiteRook;
5644             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5645     }
5646
5647     if( nrCastlingRights == -1) {
5648         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5649         /*       This sets default castling rights from none to normal corners   */
5650         /* Variants with other castling rights must set them themselves above    */
5651         nrCastlingRights = 6;
5652
5653         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5654         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5655         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5656         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5657         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5658         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5659      }
5660
5661      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5662      if(gameInfo.variant == VariantGreat) { // promotion commoners
5663         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5664         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5665         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5666         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5667      }
5668      if( gameInfo.variant == VariantSChess ) {
5669       initialPosition[1][0] = BlackMarshall;
5670       initialPosition[2][0] = BlackAngel;
5671       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5672       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5673       initialPosition[1][1] = initialPosition[2][1] = 
5674       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5675      }
5676   if (appData.debugMode) {
5677     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5678   }
5679     if(shuffleOpenings) {
5680         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5681         startedFromSetupPosition = TRUE;
5682     }
5683     if(startedFromPositionFile) {
5684       /* [HGM] loadPos: use PositionFile for every new game */
5685       CopyBoard(initialPosition, filePosition);
5686       for(i=0; i<nrCastlingRights; i++)
5687           initialRights[i] = filePosition[CASTLING][i];
5688       startedFromSetupPosition = TRUE;
5689     }
5690
5691     CopyBoard(boards[0], initialPosition);
5692
5693     if(oldx != gameInfo.boardWidth ||
5694        oldy != gameInfo.boardHeight ||
5695        oldv != gameInfo.variant ||
5696        oldh != gameInfo.holdingsWidth
5697                                          )
5698             InitDrawingSizes(-2 ,0);
5699
5700     oldv = gameInfo.variant;
5701     if (redraw)
5702       DrawPosition(TRUE, boards[currentMove]);
5703 }
5704
5705 void
5706 SendBoard(cps, moveNum)
5707      ChessProgramState *cps;
5708      int moveNum;
5709 {
5710     char message[MSG_SIZ];
5711
5712     if (cps->useSetboard) {
5713       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5714       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5715       SendToProgram(message, cps);
5716       free(fen);
5717
5718     } else {
5719       ChessSquare *bp;
5720       int i, j;
5721       /* Kludge to set black to move, avoiding the troublesome and now
5722        * deprecated "black" command.
5723        */
5724       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5725         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5726
5727       SendToProgram("edit\n", cps);
5728       SendToProgram("#\n", cps);
5729       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5730         bp = &boards[moveNum][i][BOARD_LEFT];
5731         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5732           if ((int) *bp < (int) BlackPawn) {
5733             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5734                     AAA + j, ONE + i);
5735             if(message[0] == '+' || message[0] == '~') {
5736               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5737                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5738                         AAA + j, ONE + i);
5739             }
5740             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5741                 message[1] = BOARD_RGHT   - 1 - j + '1';
5742                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5743             }
5744             SendToProgram(message, cps);
5745           }
5746         }
5747       }
5748
5749       SendToProgram("c\n", cps);
5750       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5751         bp = &boards[moveNum][i][BOARD_LEFT];
5752         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5753           if (((int) *bp != (int) EmptySquare)
5754               && ((int) *bp >= (int) BlackPawn)) {
5755             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5756                     AAA + j, ONE + i);
5757             if(message[0] == '+' || message[0] == '~') {
5758               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5759                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5760                         AAA + j, ONE + i);
5761             }
5762             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5763                 message[1] = BOARD_RGHT   - 1 - j + '1';
5764                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5765             }
5766             SendToProgram(message, cps);
5767           }
5768         }
5769       }
5770
5771       SendToProgram(".\n", cps);
5772     }
5773     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5774 }
5775
5776 ChessSquare
5777 DefaultPromoChoice(int white)
5778 {
5779     ChessSquare result;
5780     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5781         result = WhiteFerz; // no choice
5782     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5783         result= WhiteKing; // in Suicide Q is the last thing we want
5784     else if(gameInfo.variant == VariantSpartan)
5785         result = white ? WhiteQueen : WhiteAngel;
5786     else result = WhiteQueen;
5787     if(!white) result = WHITE_TO_BLACK result;
5788     return result;
5789 }
5790
5791 static int autoQueen; // [HGM] oneclick
5792
5793 int
5794 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5795 {
5796     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5797     /* [HGM] add Shogi promotions */
5798     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5799     ChessSquare piece;
5800     ChessMove moveType;
5801     Boolean premove;
5802
5803     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5804     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5805
5806     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5807       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5808         return FALSE;
5809
5810     piece = boards[currentMove][fromY][fromX];
5811     if(gameInfo.variant == VariantShogi) {
5812         promotionZoneSize = BOARD_HEIGHT/3;
5813         highestPromotingPiece = (int)WhiteFerz;
5814     } else if(gameInfo.variant == VariantMakruk) {
5815         promotionZoneSize = 3;
5816     }
5817
5818     // Treat Lance as Pawn when it is not representing Amazon
5819     if(gameInfo.variant != VariantSuper) {
5820         if(piece == WhiteLance) piece = WhitePawn; else
5821         if(piece == BlackLance) piece = BlackPawn;
5822     }
5823
5824     // next weed out all moves that do not touch the promotion zone at all
5825     if((int)piece >= BlackPawn) {
5826         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5827              return FALSE;
5828         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5829     } else {
5830         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5831            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5832     }
5833
5834     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5835
5836     // weed out mandatory Shogi promotions
5837     if(gameInfo.variant == VariantShogi) {
5838         if(piece >= BlackPawn) {
5839             if(toY == 0 && piece == BlackPawn ||
5840                toY == 0 && piece == BlackQueen ||
5841                toY <= 1 && piece == BlackKnight) {
5842                 *promoChoice = '+';
5843                 return FALSE;
5844             }
5845         } else {
5846             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5847                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5848                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5849                 *promoChoice = '+';
5850                 return FALSE;
5851             }
5852         }
5853     }
5854
5855     // weed out obviously illegal Pawn moves
5856     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5857         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5858         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5859         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5860         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5861         // note we are not allowed to test for valid (non-)capture, due to premove
5862     }
5863
5864     // we either have a choice what to promote to, or (in Shogi) whether to promote
5865     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5866         *promoChoice = PieceToChar(BlackFerz);  // no choice
5867         return FALSE;
5868     }
5869     // no sense asking what we must promote to if it is going to explode...
5870     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5871         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5872         return FALSE;
5873     }
5874     // give caller the default choice even if we will not make it
5875     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5876     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5877     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5878                            && gameInfo.variant != VariantShogi
5879                            && gameInfo.variant != VariantSuper) return FALSE;
5880     if(autoQueen) return FALSE; // predetermined
5881
5882     // suppress promotion popup on illegal moves that are not premoves
5883     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5884               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5885     if(appData.testLegality && !premove) {
5886         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5887                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5888         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5889             return FALSE;
5890     }
5891
5892     return TRUE;
5893 }
5894
5895 int
5896 InPalace(row, column)
5897      int row, column;
5898 {   /* [HGM] for Xiangqi */
5899     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5900          column < (BOARD_WIDTH + 4)/2 &&
5901          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5902     return FALSE;
5903 }
5904
5905 int
5906 PieceForSquare (x, y)
5907      int x;
5908      int y;
5909 {
5910   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5911      return -1;
5912   else
5913      return boards[currentMove][y][x];
5914 }
5915
5916 int
5917 OKToStartUserMove(x, y)
5918      int x, y;
5919 {
5920     ChessSquare from_piece;
5921     int white_piece;
5922
5923     if (matchMode) return FALSE;
5924     if (gameMode == EditPosition) return TRUE;
5925
5926     if (x >= 0 && y >= 0)
5927       from_piece = boards[currentMove][y][x];
5928     else
5929       from_piece = EmptySquare;
5930
5931     if (from_piece == EmptySquare) return FALSE;
5932
5933     white_piece = (int)from_piece >= (int)WhitePawn &&
5934       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5935
5936     switch (gameMode) {
5937       case PlayFromGameFile:
5938       case AnalyzeFile:
5939       case TwoMachinesPlay:
5940       case EndOfGame:
5941         return FALSE;
5942
5943       case IcsObserving:
5944       case IcsIdle:
5945         return FALSE;
5946
5947       case MachinePlaysWhite:
5948       case IcsPlayingBlack:
5949         if (appData.zippyPlay) return FALSE;
5950         if (white_piece) {
5951             DisplayMoveError(_("You are playing Black"));
5952             return FALSE;
5953         }
5954         break;
5955
5956       case MachinePlaysBlack:
5957       case IcsPlayingWhite:
5958         if (appData.zippyPlay) return FALSE;
5959         if (!white_piece) {
5960             DisplayMoveError(_("You are playing White"));
5961             return FALSE;
5962         }
5963         break;
5964
5965       case EditGame:
5966         if (!white_piece && WhiteOnMove(currentMove)) {
5967             DisplayMoveError(_("It is White's turn"));
5968             return FALSE;
5969         }
5970         if (white_piece && !WhiteOnMove(currentMove)) {
5971             DisplayMoveError(_("It is Black's turn"));
5972             return FALSE;
5973         }
5974         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5975             /* Editing correspondence game history */
5976             /* Could disallow this or prompt for confirmation */
5977             cmailOldMove = -1;
5978         }
5979         break;
5980
5981       case BeginningOfGame:
5982         if (appData.icsActive) return FALSE;
5983         if (!appData.noChessProgram) {
5984             if (!white_piece) {
5985                 DisplayMoveError(_("You are playing White"));
5986                 return FALSE;
5987             }
5988         }
5989         break;
5990
5991       case Training:
5992         if (!white_piece && WhiteOnMove(currentMove)) {
5993             DisplayMoveError(_("It is White's turn"));
5994             return FALSE;
5995         }
5996         if (white_piece && !WhiteOnMove(currentMove)) {
5997             DisplayMoveError(_("It is Black's turn"));
5998             return FALSE;
5999         }
6000         break;
6001
6002       default:
6003       case IcsExamining:
6004         break;
6005     }
6006     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6007         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6008         && gameMode != AnalyzeFile && gameMode != Training) {
6009         DisplayMoveError(_("Displayed position is not current"));
6010         return FALSE;
6011     }
6012     return TRUE;
6013 }
6014
6015 Boolean
6016 OnlyMove(int *x, int *y, Boolean captures) {
6017     DisambiguateClosure cl;
6018     if (appData.zippyPlay) return FALSE;
6019     switch(gameMode) {
6020       case MachinePlaysBlack:
6021       case IcsPlayingWhite:
6022       case BeginningOfGame:
6023         if(!WhiteOnMove(currentMove)) return FALSE;
6024         break;
6025       case MachinePlaysWhite:
6026       case IcsPlayingBlack:
6027         if(WhiteOnMove(currentMove)) return FALSE;
6028         break;
6029       case EditGame:
6030         break;
6031       default:
6032         return FALSE;
6033     }
6034     cl.pieceIn = EmptySquare;
6035     cl.rfIn = *y;
6036     cl.ffIn = *x;
6037     cl.rtIn = -1;
6038     cl.ftIn = -1;
6039     cl.promoCharIn = NULLCHAR;
6040     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6041     if( cl.kind == NormalMove ||
6042         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6043         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6044         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6045       fromX = cl.ff;
6046       fromY = cl.rf;
6047       *x = cl.ft;
6048       *y = cl.rt;
6049       return TRUE;
6050     }
6051     if(cl.kind != ImpossibleMove) return FALSE;
6052     cl.pieceIn = EmptySquare;
6053     cl.rfIn = -1;
6054     cl.ffIn = -1;
6055     cl.rtIn = *y;
6056     cl.ftIn = *x;
6057     cl.promoCharIn = NULLCHAR;
6058     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6059     if( cl.kind == NormalMove ||
6060         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6061         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6062         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6063       fromX = cl.ff;
6064       fromY = cl.rf;
6065       *x = cl.ft;
6066       *y = cl.rt;
6067       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6068       return TRUE;
6069     }
6070     return FALSE;
6071 }
6072
6073 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6074 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6075 int lastLoadGameUseList = FALSE;
6076 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6077 ChessMove lastLoadGameStart = EndOfFile;
6078
6079 void
6080 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6081      int fromX, fromY, toX, toY;
6082      int promoChar;
6083 {
6084     ChessMove moveType;
6085     ChessSquare pdown, pup;
6086
6087     /* Check if the user is playing in turn.  This is complicated because we
6088        let the user "pick up" a piece before it is his turn.  So the piece he
6089        tried to pick up may have been captured by the time he puts it down!
6090        Therefore we use the color the user is supposed to be playing in this
6091        test, not the color of the piece that is currently on the starting
6092        square---except in EditGame mode, where the user is playing both
6093        sides; fortunately there the capture race can't happen.  (It can
6094        now happen in IcsExamining mode, but that's just too bad.  The user
6095        will get a somewhat confusing message in that case.)
6096        */
6097
6098     switch (gameMode) {
6099       case PlayFromGameFile:
6100       case AnalyzeFile:
6101       case TwoMachinesPlay:
6102       case EndOfGame:
6103       case IcsObserving:
6104       case IcsIdle:
6105         /* We switched into a game mode where moves are not accepted,
6106            perhaps while the mouse button was down. */
6107         return;
6108
6109       case MachinePlaysWhite:
6110         /* User is moving for Black */
6111         if (WhiteOnMove(currentMove)) {
6112             DisplayMoveError(_("It is White's turn"));
6113             return;
6114         }
6115         break;
6116
6117       case MachinePlaysBlack:
6118         /* User is moving for White */
6119         if (!WhiteOnMove(currentMove)) {
6120             DisplayMoveError(_("It is Black's turn"));
6121             return;
6122         }
6123         break;
6124
6125       case EditGame:
6126       case IcsExamining:
6127       case BeginningOfGame:
6128       case AnalyzeMode:
6129       case Training:
6130         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6131         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6132             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6133             /* User is moving for Black */
6134             if (WhiteOnMove(currentMove)) {
6135                 DisplayMoveError(_("It is White's turn"));
6136                 return;
6137             }
6138         } else {
6139             /* User is moving for White */
6140             if (!WhiteOnMove(currentMove)) {
6141                 DisplayMoveError(_("It is Black's turn"));
6142                 return;
6143             }
6144         }
6145         break;
6146
6147       case IcsPlayingBlack:
6148         /* User is moving for Black */
6149         if (WhiteOnMove(currentMove)) {
6150             if (!appData.premove) {
6151                 DisplayMoveError(_("It is White's turn"));
6152             } else if (toX >= 0 && toY >= 0) {
6153                 premoveToX = toX;
6154                 premoveToY = toY;
6155                 premoveFromX = fromX;
6156                 premoveFromY = fromY;
6157                 premovePromoChar = promoChar;
6158                 gotPremove = 1;
6159                 if (appData.debugMode)
6160                     fprintf(debugFP, "Got premove: fromX %d,"
6161                             "fromY %d, toX %d, toY %d\n",
6162                             fromX, fromY, toX, toY);
6163             }
6164             return;
6165         }
6166         break;
6167
6168       case IcsPlayingWhite:
6169         /* User is moving for White */
6170         if (!WhiteOnMove(currentMove)) {
6171             if (!appData.premove) {
6172                 DisplayMoveError(_("It is Black's turn"));
6173             } else if (toX >= 0 && toY >= 0) {
6174                 premoveToX = toX;
6175                 premoveToY = toY;
6176                 premoveFromX = fromX;
6177                 premoveFromY = fromY;
6178                 premovePromoChar = promoChar;
6179                 gotPremove = 1;
6180                 if (appData.debugMode)
6181                     fprintf(debugFP, "Got premove: fromX %d,"
6182                             "fromY %d, toX %d, toY %d\n",
6183                             fromX, fromY, toX, toY);
6184             }
6185             return;
6186         }
6187         break;
6188
6189       default:
6190         break;
6191
6192       case EditPosition:
6193         /* EditPosition, empty square, or different color piece;
6194            click-click move is possible */
6195         if (toX == -2 || toY == -2) {
6196             boards[0][fromY][fromX] = EmptySquare;
6197             DrawPosition(FALSE, boards[currentMove]);
6198             return;
6199         } else if (toX >= 0 && toY >= 0) {
6200             boards[0][toY][toX] = boards[0][fromY][fromX];
6201             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6202                 if(boards[0][fromY][0] != EmptySquare) {
6203                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6204                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6205                 }
6206             } else
6207             if(fromX == BOARD_RGHT+1) {
6208                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6209                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6210                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6211                 }
6212             } else
6213             boards[0][fromY][fromX] = EmptySquare;
6214             DrawPosition(FALSE, boards[currentMove]);
6215             return;
6216         }
6217         return;
6218     }
6219
6220     if(toX < 0 || toY < 0) return;
6221     pdown = boards[currentMove][fromY][fromX];
6222     pup = boards[currentMove][toY][toX];
6223
6224     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6225     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6226          if( pup != EmptySquare ) return;
6227          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6228            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6229                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6230            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6231            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6232            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6233            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6234          fromY = DROP_RANK;
6235     }
6236
6237     /* [HGM] always test for legality, to get promotion info */
6238     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6239                                          fromY, fromX, toY, toX, promoChar);
6240     /* [HGM] but possibly ignore an IllegalMove result */
6241     if (appData.testLegality) {
6242         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6243             DisplayMoveError(_("Illegal move"));
6244             return;
6245         }
6246     }
6247
6248     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6249 }
6250
6251 /* Common tail of UserMoveEvent and DropMenuEvent */
6252 int
6253 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6254      ChessMove moveType;
6255      int fromX, fromY, toX, toY;
6256      /*char*/int promoChar;
6257 {
6258     char *bookHit = 0;
6259
6260     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6261         // [HGM] superchess: suppress promotions to non-available piece
6262         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6263         if(WhiteOnMove(currentMove)) {
6264             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6265         } else {
6266             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6267         }
6268     }
6269
6270     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6271        move type in caller when we know the move is a legal promotion */
6272     if(moveType == NormalMove && promoChar)
6273         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6274
6275     /* [HGM] <popupFix> The following if has been moved here from
6276        UserMoveEvent(). Because it seemed to belong here (why not allow
6277        piece drops in training games?), and because it can only be
6278        performed after it is known to what we promote. */
6279     if (gameMode == Training) {
6280       /* compare the move played on the board to the next move in the
6281        * game. If they match, display the move and the opponent's response.
6282        * If they don't match, display an error message.
6283        */
6284       int saveAnimate;
6285       Board testBoard;
6286       CopyBoard(testBoard, boards[currentMove]);
6287       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6288
6289       if (CompareBoards(testBoard, boards[currentMove+1])) {
6290         ForwardInner(currentMove+1);
6291
6292         /* Autoplay the opponent's response.
6293          * if appData.animate was TRUE when Training mode was entered,
6294          * the response will be animated.
6295          */
6296         saveAnimate = appData.animate;
6297         appData.animate = animateTraining;
6298         ForwardInner(currentMove+1);
6299         appData.animate = saveAnimate;
6300
6301         /* check for the end of the game */
6302         if (currentMove >= forwardMostMove) {
6303           gameMode = PlayFromGameFile;
6304           ModeHighlight();
6305           SetTrainingModeOff();
6306           DisplayInformation(_("End of game"));
6307         }
6308       } else {
6309         DisplayError(_("Incorrect move"), 0);
6310       }
6311       return 1;
6312     }
6313
6314   /* Ok, now we know that the move is good, so we can kill
6315      the previous line in Analysis Mode */
6316   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6317                                 && currentMove < forwardMostMove) {
6318     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6319     else forwardMostMove = currentMove;
6320   }
6321
6322   /* If we need the chess program but it's dead, restart it */
6323   ResurrectChessProgram();
6324
6325   /* A user move restarts a paused game*/
6326   if (pausing)
6327     PauseEvent();
6328
6329   thinkOutput[0] = NULLCHAR;
6330
6331   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6332
6333   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6334     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6335     return 1;
6336   }
6337
6338   if (gameMode == BeginningOfGame) {
6339     if (appData.noChessProgram) {
6340       gameMode = EditGame;
6341       SetGameInfo();
6342     } else {
6343       char buf[MSG_SIZ];
6344       gameMode = MachinePlaysBlack;
6345       StartClocks();
6346       SetGameInfo();
6347       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6348       DisplayTitle(buf);
6349       if (first.sendName) {
6350         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6351         SendToProgram(buf, &first);
6352       }
6353       StartClocks();
6354     }
6355     ModeHighlight();
6356   }
6357
6358   /* Relay move to ICS or chess engine */
6359   if (appData.icsActive) {
6360     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6361         gameMode == IcsExamining) {
6362       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6363         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6364         SendToICS("draw ");
6365         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6366       }
6367       // also send plain move, in case ICS does not understand atomic claims
6368       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6369       ics_user_moved = 1;
6370     }
6371   } else {
6372     if (first.sendTime && (gameMode == BeginningOfGame ||
6373                            gameMode == MachinePlaysWhite ||
6374                            gameMode == MachinePlaysBlack)) {
6375       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6376     }
6377     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6378          // [HGM] book: if program might be playing, let it use book
6379         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6380         first.maybeThinking = TRUE;
6381     } else SendMoveToProgram(forwardMostMove-1, &first);
6382     if (currentMove == cmailOldMove + 1) {
6383       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6384     }
6385   }
6386
6387   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6388
6389   switch (gameMode) {
6390   case EditGame:
6391     if(appData.testLegality)
6392     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6393     case MT_NONE:
6394     case MT_CHECK:
6395       break;
6396     case MT_CHECKMATE:
6397     case MT_STAINMATE:
6398       if (WhiteOnMove(currentMove)) {
6399         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6400       } else {
6401         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6402       }
6403       break;
6404     case MT_STALEMATE:
6405       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6406       break;
6407     }
6408     break;
6409
6410   case MachinePlaysBlack:
6411   case MachinePlaysWhite:
6412     /* disable certain menu options while machine is thinking */
6413     SetMachineThinkingEnables();
6414     break;
6415
6416   default:
6417     break;
6418   }
6419
6420   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6421   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6422
6423   if(bookHit) { // [HGM] book: simulate book reply
6424         static char bookMove[MSG_SIZ]; // a bit generous?
6425
6426         programStats.nodes = programStats.depth = programStats.time =
6427         programStats.score = programStats.got_only_move = 0;
6428         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6429
6430         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6431         strcat(bookMove, bookHit);
6432         HandleMachineMove(bookMove, &first);
6433   }
6434   return 1;
6435 }
6436
6437 void
6438 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6439      Board board;
6440      int flags;
6441      ChessMove kind;
6442      int rf, ff, rt, ft;
6443      VOIDSTAR closure;
6444 {
6445     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6446     Markers *m = (Markers *) closure;
6447     if(rf == fromY && ff == fromX)
6448         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6449                          || kind == WhiteCapturesEnPassant
6450                          || kind == BlackCapturesEnPassant);
6451     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6452 }
6453
6454 void
6455 MarkTargetSquares(int clear)
6456 {
6457   int x, y;
6458   if(!appData.markers || !appData.highlightDragging ||
6459      !appData.testLegality || gameMode == EditPosition) return;
6460   if(clear) {
6461     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6462   } else {
6463     int capt = 0;
6464     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6465     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6466       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6467       if(capt)
6468       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6469     }
6470   }
6471   DrawPosition(TRUE, NULL);
6472 }
6473
6474 int
6475 Explode(Board board, int fromX, int fromY, int toX, int toY)
6476 {
6477     if(gameInfo.variant == VariantAtomic &&
6478        (board[toY][toX] != EmptySquare ||                     // capture?
6479         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6480                          board[fromY][fromX] == BlackPawn   )
6481       )) {
6482         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6483         return TRUE;
6484     }
6485     return FALSE;
6486 }
6487
6488 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6489
6490 int CanPromote(ChessSquare piece, int y)
6491 {
6492         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6493         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6494         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6495            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6496            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6497                                                   gameInfo.variant == VariantMakruk) return FALSE;
6498         return (piece == BlackPawn && y == 1 ||
6499                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6500                 piece == BlackLance && y == 1 ||
6501                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6502 }
6503
6504 void LeftClick(ClickType clickType, int xPix, int yPix)
6505 {
6506     int x, y;
6507     Boolean saveAnimate;
6508     static int second = 0, promotionChoice = 0, clearFlag = 0;
6509     char promoChoice = NULLCHAR;
6510     ChessSquare piece;
6511
6512     if(appData.seekGraph && appData.icsActive && loggedOn &&
6513         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6514         SeekGraphClick(clickType, xPix, yPix, 0);
6515         return;
6516     }
6517
6518     if (clickType == Press) ErrorPopDown();
6519     MarkTargetSquares(1);
6520
6521     x = EventToSquare(xPix, BOARD_WIDTH);
6522     y = EventToSquare(yPix, BOARD_HEIGHT);
6523     if (!flipView && y >= 0) {
6524         y = BOARD_HEIGHT - 1 - y;
6525     }
6526     if (flipView && x >= 0) {
6527         x = BOARD_WIDTH - 1 - x;
6528     }
6529
6530     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6531         defaultPromoChoice = promoSweep;
6532         promoSweep = EmptySquare;   // terminate sweep
6533         promoDefaultAltered = TRUE;
6534         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6535     }
6536
6537     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6538         if(clickType == Release) return; // ignore upclick of click-click destination
6539         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6540         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6541         if(gameInfo.holdingsWidth &&
6542                 (WhiteOnMove(currentMove)
6543                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6544                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6545             // click in right holdings, for determining promotion piece
6546             ChessSquare p = boards[currentMove][y][x];
6547             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6548             if(p != EmptySquare) {
6549                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6550                 fromX = fromY = -1;
6551                 return;
6552             }
6553         }
6554         DrawPosition(FALSE, boards[currentMove]);
6555         return;
6556     }
6557
6558     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6559     if(clickType == Press
6560             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6561               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6562               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6563         return;
6564
6565     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6566         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6567
6568     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6569         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6570                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6571         defaultPromoChoice = DefaultPromoChoice(side);
6572     }
6573
6574     autoQueen = appData.alwaysPromoteToQueen;
6575
6576     if (fromX == -1) {
6577       int originalY = y;
6578       gatingPiece = EmptySquare;
6579       if (clickType != Press) {
6580         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6581             DragPieceEnd(xPix, yPix); dragging = 0;
6582             DrawPosition(FALSE, NULL);
6583         }
6584         return;
6585       }
6586       fromX = x; fromY = y;
6587       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6588          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6589          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6590             /* First square */
6591             if (OKToStartUserMove(fromX, fromY)) {
6592                 second = 0;
6593                 MarkTargetSquares(0);
6594                 DragPieceBegin(xPix, yPix); dragging = 1;
6595                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6596                     promoSweep = defaultPromoChoice;
6597                     selectFlag = 0; lastX = xPix; lastY = yPix;
6598                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6599                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6600                 }
6601                 if (appData.highlightDragging) {
6602                     SetHighlights(fromX, fromY, -1, -1);
6603                 }
6604             } else fromX = fromY = -1;
6605             return;
6606         }
6607     }
6608
6609     /* fromX != -1 */
6610     if (clickType == Press && gameMode != EditPosition) {
6611         ChessSquare fromP;
6612         ChessSquare toP;
6613         int frc;
6614
6615         // ignore off-board to clicks
6616         if(y < 0 || x < 0) return;
6617
6618         /* Check if clicking again on the same color piece */
6619         fromP = boards[currentMove][fromY][fromX];
6620         toP = boards[currentMove][y][x];
6621         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6622         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6623              WhitePawn <= toP && toP <= WhiteKing &&
6624              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6625              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6626             (BlackPawn <= fromP && fromP <= BlackKing &&
6627              BlackPawn <= toP && toP <= BlackKing &&
6628              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6629              !(fromP == BlackKing && toP == BlackRook && frc))) {
6630             /* Clicked again on same color piece -- changed his mind */
6631             second = (x == fromX && y == fromY);
6632             promoDefaultAltered = FALSE;
6633            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6634             if (appData.highlightDragging) {
6635                 SetHighlights(x, y, -1, -1);
6636             } else {
6637                 ClearHighlights();
6638             }
6639             if (OKToStartUserMove(x, y)) {
6640                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6641                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6642                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6643                  gatingPiece = boards[currentMove][fromY][fromX];
6644                 else gatingPiece = EmptySquare;
6645                 fromX = x;
6646                 fromY = y; dragging = 1;
6647                 MarkTargetSquares(0);
6648                 DragPieceBegin(xPix, yPix);
6649                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6650                     promoSweep = defaultPromoChoice;
6651                     selectFlag = 0; lastX = xPix; lastY = yPix;
6652                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6653                 }
6654             }
6655            }
6656            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6657            second = FALSE; 
6658         }
6659         // ignore clicks on holdings
6660         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6661     }
6662
6663     if (clickType == Release && x == fromX && y == fromY) {
6664         DragPieceEnd(xPix, yPix); dragging = 0;
6665         if(clearFlag) {
6666             // a deferred attempt to click-click move an empty square on top of a piece
6667             boards[currentMove][y][x] = EmptySquare;
6668             ClearHighlights();
6669             DrawPosition(FALSE, boards[currentMove]);
6670             fromX = fromY = -1; clearFlag = 0;
6671             return;
6672         }
6673         if (appData.animateDragging) {
6674             /* Undo animation damage if any */
6675             DrawPosition(FALSE, NULL);
6676         }
6677         if (second) {
6678             /* Second up/down in same square; just abort move */
6679             second = 0;
6680             fromX = fromY = -1;
6681             gatingPiece = EmptySquare;
6682             ClearHighlights();
6683             gotPremove = 0;
6684             ClearPremoveHighlights();
6685         } else {
6686             /* First upclick in same square; start click-click mode */
6687             SetHighlights(x, y, -1, -1);
6688         }
6689         return;
6690     }
6691
6692     clearFlag = 0;
6693
6694     /* we now have a different from- and (possibly off-board) to-square */
6695     /* Completed move */
6696     toX = x;
6697     toY = y;
6698     saveAnimate = appData.animate;
6699     if (clickType == Press) {
6700         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6701             // must be Edit Position mode with empty-square selected
6702             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6703             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6704             return;
6705         }
6706         /* Finish clickclick move */
6707         if (appData.animate || appData.highlightLastMove) {
6708             SetHighlights(fromX, fromY, toX, toY);
6709         } else {
6710             ClearHighlights();
6711         }
6712     } else {
6713         /* Finish drag move */
6714         if (appData.highlightLastMove) {
6715             SetHighlights(fromX, fromY, toX, toY);
6716         } else {
6717             ClearHighlights();
6718         }
6719         DragPieceEnd(xPix, yPix); dragging = 0;
6720         /* Don't animate move and drag both */
6721         appData.animate = FALSE;
6722     }
6723
6724     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6725     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6726         ChessSquare piece = boards[currentMove][fromY][fromX];
6727         if(gameMode == EditPosition && piece != EmptySquare &&
6728            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6729             int n;
6730
6731             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6732                 n = PieceToNumber(piece - (int)BlackPawn);
6733                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6734                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6735                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6736             } else
6737             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6738                 n = PieceToNumber(piece);
6739                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6740                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6741                 boards[currentMove][n][BOARD_WIDTH-2]++;
6742             }
6743             boards[currentMove][fromY][fromX] = EmptySquare;
6744         }
6745         ClearHighlights();
6746         fromX = fromY = -1;
6747         DrawPosition(TRUE, boards[currentMove]);
6748         return;
6749     }
6750
6751     // off-board moves should not be highlighted
6752     if(x < 0 || y < 0) ClearHighlights();
6753
6754     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6755
6756     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6757         SetHighlights(fromX, fromY, toX, toY);
6758         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6759             // [HGM] super: promotion to captured piece selected from holdings
6760             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6761             promotionChoice = TRUE;
6762             // kludge follows to temporarily execute move on display, without promoting yet
6763             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6764             boards[currentMove][toY][toX] = p;
6765             DrawPosition(FALSE, boards[currentMove]);
6766             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6767             boards[currentMove][toY][toX] = q;
6768             DisplayMessage("Click in holdings to choose piece", "");
6769             return;
6770         }
6771         PromotionPopUp();
6772     } else {
6773         int oldMove = currentMove;
6774         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6775         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6776         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6777         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6778            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6779             DrawPosition(TRUE, boards[currentMove]);
6780         fromX = fromY = -1;
6781     }
6782     appData.animate = saveAnimate;
6783     if (appData.animate || appData.animateDragging) {
6784         /* Undo animation damage if needed */
6785         DrawPosition(FALSE, NULL);
6786     }
6787 }
6788
6789 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6790 {   // front-end-free part taken out of PieceMenuPopup
6791     int whichMenu; int xSqr, ySqr;
6792
6793     if(seekGraphUp) { // [HGM] seekgraph
6794         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6795         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6796         return -2;
6797     }
6798
6799     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6800          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6801         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6802         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6803         if(action == Press)   {
6804             originalFlip = flipView;
6805             flipView = !flipView; // temporarily flip board to see game from partners perspective
6806             DrawPosition(TRUE, partnerBoard);
6807             DisplayMessage(partnerStatus, "");
6808             partnerUp = TRUE;
6809         } else if(action == Release) {
6810             flipView = originalFlip;
6811             DrawPosition(TRUE, boards[currentMove]);
6812             partnerUp = FALSE;
6813         }
6814         return -2;
6815     }
6816
6817     xSqr = EventToSquare(x, BOARD_WIDTH);
6818     ySqr = EventToSquare(y, BOARD_HEIGHT);
6819     if (action == Release) {
6820         if(pieceSweep != EmptySquare) {
6821             EditPositionMenuEvent(pieceSweep, toX, toY);
6822             pieceSweep = EmptySquare;
6823         } else UnLoadPV(); // [HGM] pv
6824     }
6825     if (action != Press) return -2; // return code to be ignored
6826     switch (gameMode) {
6827       case IcsExamining:
6828         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6829       case EditPosition:
6830         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6831         if (xSqr < 0 || ySqr < 0) return -1;
6832         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6833         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6834         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6835         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6836         NextPiece(0);
6837         return -2;\r
6838       case IcsObserving:
6839         if(!appData.icsEngineAnalyze) return -1;
6840       case IcsPlayingWhite:
6841       case IcsPlayingBlack:
6842         if(!appData.zippyPlay) goto noZip;
6843       case AnalyzeMode:
6844       case AnalyzeFile:
6845       case MachinePlaysWhite:
6846       case MachinePlaysBlack:
6847       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6848         if (!appData.dropMenu) {
6849           LoadPV(x, y);
6850           return 2; // flag front-end to grab mouse events
6851         }
6852         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6853            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6854       case EditGame:
6855       noZip:
6856         if (xSqr < 0 || ySqr < 0) return -1;
6857         if (!appData.dropMenu || appData.testLegality &&
6858             gameInfo.variant != VariantBughouse &&
6859             gameInfo.variant != VariantCrazyhouse) return -1;
6860         whichMenu = 1; // drop menu
6861         break;
6862       default:
6863         return -1;
6864     }
6865
6866     if (((*fromX = xSqr) < 0) ||
6867         ((*fromY = ySqr) < 0)) {
6868         *fromX = *fromY = -1;
6869         return -1;
6870     }
6871     if (flipView)
6872       *fromX = BOARD_WIDTH - 1 - *fromX;
6873     else
6874       *fromY = BOARD_HEIGHT - 1 - *fromY;
6875
6876     return whichMenu;
6877 }
6878
6879 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6880 {
6881 //    char * hint = lastHint;
6882     FrontEndProgramStats stats;
6883
6884     stats.which = cps == &first ? 0 : 1;
6885     stats.depth = cpstats->depth;
6886     stats.nodes = cpstats->nodes;
6887     stats.score = cpstats->score;
6888     stats.time = cpstats->time;
6889     stats.pv = cpstats->movelist;
6890     stats.hint = lastHint;
6891     stats.an_move_index = 0;
6892     stats.an_move_count = 0;
6893
6894     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6895         stats.hint = cpstats->move_name;
6896         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6897         stats.an_move_count = cpstats->nr_moves;
6898     }
6899
6900     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
6901
6902     SetProgramStats( &stats );
6903 }
6904
6905 void
6906 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6907 {       // count all piece types
6908         int p, f, r;
6909         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6910         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6911         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6912                 p = board[r][f];
6913                 pCnt[p]++;
6914                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6915                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6916                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6917                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6918                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6919                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6920         }
6921 }
6922
6923 int
6924 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6925 {
6926         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6927         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6928
6929         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6930         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6931         if(myPawns == 2 && nMine == 3) // KPP
6932             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6933         if(myPawns == 1 && nMine == 2) // KP
6934             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6935         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6936             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6937         if(myPawns) return FALSE;
6938         if(pCnt[WhiteRook+side])
6939             return pCnt[BlackRook-side] ||
6940                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6941                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6942                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6943         if(pCnt[WhiteCannon+side]) {
6944             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6945             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6946         }
6947         if(pCnt[WhiteKnight+side])
6948             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6949         return FALSE;
6950 }
6951
6952 int
6953 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6954 {
6955         VariantClass v = gameInfo.variant;
6956
6957         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6958         if(v == VariantShatranj) return TRUE; // always winnable through baring
6959         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6960         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6961
6962         if(v == VariantXiangqi) {
6963                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6964
6965                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6966                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6967                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6968                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6969                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6970                 if(stale) // we have at least one last-rank P plus perhaps C
6971                     return majors // KPKX
6972                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6973                 else // KCA*E*
6974                     return pCnt[WhiteFerz+side] // KCAK
6975                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6976                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6977                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6978
6979         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6980                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6981
6982                 if(nMine == 1) return FALSE; // bare King
6983                 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
6984                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6985                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6986                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6987                 if(pCnt[WhiteKnight+side])
6988                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6989                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6990                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6991                 if(nBishops)
6992                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6993                 if(pCnt[WhiteAlfil+side])
6994                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6995                 if(pCnt[WhiteWazir+side])
6996                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6997         }
6998
6999         return TRUE;
7000 }
7001
7002 int
7003 Adjudicate(ChessProgramState *cps)
7004 {       // [HGM] some adjudications useful with buggy engines
7005         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7006         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7007         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7008         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7009         int k, count = 0; static int bare = 1;
7010         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7011         Boolean canAdjudicate = !appData.icsActive;
7012
7013         // most tests only when we understand the game, i.e. legality-checking on
7014             if( appData.testLegality )
7015             {   /* [HGM] Some more adjudications for obstinate engines */
7016                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7017                 static int moveCount = 6;
7018                 ChessMove result;
7019                 char *reason = NULL;
7020
7021                 /* Count what is on board. */
7022                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7023
7024                 /* Some material-based adjudications that have to be made before stalemate test */
7025                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7026                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7027                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7028                      if(canAdjudicate && appData.checkMates) {
7029                          if(engineOpponent)
7030                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7031                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7032                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7033                          return 1;
7034                      }
7035                 }
7036
7037                 /* Bare King in Shatranj (loses) or Losers (wins) */
7038                 if( nrW == 1 || nrB == 1) {
7039                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7040                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7041                      if(canAdjudicate && appData.checkMates) {
7042                          if(engineOpponent)
7043                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7044                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7045                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7046                          return 1;
7047                      }
7048                   } else
7049                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7050                   {    /* bare King */
7051                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7052                         if(canAdjudicate && appData.checkMates) {
7053                             /* but only adjudicate if adjudication enabled */
7054                             if(engineOpponent)
7055                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7056                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7057                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7058                             return 1;
7059                         }
7060                   }
7061                 } else bare = 1;
7062
7063
7064             // don't wait for engine to announce game end if we can judge ourselves
7065             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7066               case MT_CHECK:
7067                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7068                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7069                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7070                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7071                             checkCnt++;
7072                         if(checkCnt >= 2) {
7073                             reason = "Xboard adjudication: 3rd check";
7074                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7075                             break;
7076                         }
7077                     }
7078                 }
7079               case MT_NONE:
7080               default:
7081                 break;
7082               case MT_STALEMATE:
7083               case MT_STAINMATE:
7084                 reason = "Xboard adjudication: Stalemate";
7085                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7086                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7087                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7088                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7089                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7090                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7091                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7092                                                                         EP_CHECKMATE : EP_WINS);
7093                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7094                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7095                 }
7096                 break;
7097               case MT_CHECKMATE:
7098                 reason = "Xboard adjudication: Checkmate";
7099                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7100                 break;
7101             }
7102
7103                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7104                     case EP_STALEMATE:
7105                         result = GameIsDrawn; break;
7106                     case EP_CHECKMATE:
7107                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7108                     case EP_WINS:
7109                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7110                     default:
7111                         result = EndOfFile;
7112                 }
7113                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7114                     if(engineOpponent)
7115                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7116                     GameEnds( result, reason, GE_XBOARD );
7117                     return 1;
7118                 }
7119
7120                 /* Next absolutely insufficient mating material. */
7121                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7122                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7123                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7124
7125                      /* always flag draws, for judging claims */
7126                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7127
7128                      if(canAdjudicate && appData.materialDraws) {
7129                          /* but only adjudicate them if adjudication enabled */
7130                          if(engineOpponent) {
7131                            SendToProgram("force\n", engineOpponent); // suppress reply
7132                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7133                          }
7134                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7135                          return 1;
7136                      }
7137                 }
7138
7139                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7140                 if(gameInfo.variant == VariantXiangqi ?
7141                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7142                  : nrW + nrB == 4 &&
7143                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7144                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7145                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7146                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7147                    ) ) {
7148                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7149                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7150                           if(engineOpponent) {
7151                             SendToProgram("force\n", engineOpponent); // suppress reply
7152                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7153                           }
7154                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7155                           return 1;
7156                      }
7157                 } else moveCount = 6;
7158             }
7159         if (appData.debugMode) { int i;
7160             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7161                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7162                     appData.drawRepeats);
7163             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7164               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7165
7166         }
7167
7168         // Repetition draws and 50-move rule can be applied independently of legality testing
7169
7170                 /* Check for rep-draws */
7171                 count = 0;
7172                 for(k = forwardMostMove-2;
7173                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7174                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7175                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7176                     k-=2)
7177                 {   int rights=0;
7178                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7179                         /* compare castling rights */
7180                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7181                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7182                                 rights++; /* King lost rights, while rook still had them */
7183                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7184                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7185                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7186                                    rights++; /* but at least one rook lost them */
7187                         }
7188                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7189                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7190                                 rights++;
7191                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7192                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7193                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7194                                    rights++;
7195                         }
7196                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7197                             && appData.drawRepeats > 1) {
7198                              /* adjudicate after user-specified nr of repeats */
7199                              int result = GameIsDrawn;
7200                              char *details = "XBoard adjudication: repetition draw";
7201                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7202                                 // [HGM] xiangqi: check for forbidden perpetuals
7203                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7204                                 for(m=forwardMostMove; m>k; m-=2) {
7205                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7206                                         ourPerpetual = 0; // the current mover did not always check
7207                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7208                                         hisPerpetual = 0; // the opponent did not always check
7209                                 }
7210                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7211                                                                         ourPerpetual, hisPerpetual);
7212                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7213                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7214                                     details = "Xboard adjudication: perpetual checking";
7215                                 } else
7216                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7217                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7218                                 } else
7219                                 // Now check for perpetual chases
7220                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7221                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7222                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7223                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7224                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7225                                         details = "Xboard adjudication: perpetual chasing";
7226                                     } else
7227                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7228                                         break; // Abort repetition-checking loop.
7229                                 }
7230                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7231                              }
7232                              if(engineOpponent) {
7233                                SendToProgram("force\n", engineOpponent); // suppress reply
7234                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7235                              }
7236                              GameEnds( result, details, GE_XBOARD );
7237                              return 1;
7238                         }
7239                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7240                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7241                     }
7242                 }
7243
7244                 /* Now we test for 50-move draws. Determine ply count */
7245                 count = forwardMostMove;
7246                 /* look for last irreversble move */
7247                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7248                     count--;
7249                 /* if we hit starting position, add initial plies */
7250                 if( count == backwardMostMove )
7251                     count -= initialRulePlies;
7252                 count = forwardMostMove - count;
7253                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7254                         // adjust reversible move counter for checks in Xiangqi
7255                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7256                         if(i < backwardMostMove) i = backwardMostMove;
7257                         while(i <= forwardMostMove) {
7258                                 lastCheck = inCheck; // check evasion does not count
7259                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7260                                 if(inCheck || lastCheck) count--; // check does not count
7261                                 i++;
7262                         }
7263                 }
7264                 if( count >= 100)
7265                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7266                          /* this is used to judge if draw claims are legal */
7267                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7268                          if(engineOpponent) {
7269                            SendToProgram("force\n", engineOpponent); // suppress reply
7270                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7271                          }
7272                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7273                          return 1;
7274                 }
7275
7276                 /* if draw offer is pending, treat it as a draw claim
7277                  * when draw condition present, to allow engines a way to
7278                  * claim draws before making their move to avoid a race
7279                  * condition occurring after their move
7280                  */
7281                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7282                          char *p = NULL;
7283                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7284                              p = "Draw claim: 50-move rule";
7285                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7286                              p = "Draw claim: 3-fold repetition";
7287                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7288                              p = "Draw claim: insufficient mating material";
7289                          if( p != NULL && canAdjudicate) {
7290                              if(engineOpponent) {
7291                                SendToProgram("force\n", engineOpponent); // suppress reply
7292                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7293                              }
7294                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7295                              return 1;
7296                          }
7297                 }
7298
7299                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7300                     if(engineOpponent) {
7301                       SendToProgram("force\n", engineOpponent); // suppress reply
7302                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7303                     }
7304                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7305                     return 1;
7306                 }
7307         return 0;
7308 }
7309
7310 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7311 {   // [HGM] book: this routine intercepts moves to simulate book replies
7312     char *bookHit = NULL;
7313
7314     //first determine if the incoming move brings opponent into his book
7315     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7316         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7317     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7318     if(bookHit != NULL && !cps->bookSuspend) {
7319         // make sure opponent is not going to reply after receiving move to book position
7320         SendToProgram("force\n", cps);
7321         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7322     }
7323     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7324     // now arrange restart after book miss
7325     if(bookHit) {
7326         // after a book hit we never send 'go', and the code after the call to this routine
7327         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7328         char buf[MSG_SIZ];
7329         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7330         SendToProgram(buf, cps);
7331         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7332     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7333         SendToProgram("go\n", cps);
7334         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7335     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7336         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7337             SendToProgram("go\n", cps);
7338         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7339     }
7340     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7341 }
7342
7343 char *savedMessage;
7344 ChessProgramState *savedState;
7345 void DeferredBookMove(void)
7346 {
7347         if(savedState->lastPing != savedState->lastPong)
7348                     ScheduleDelayedEvent(DeferredBookMove, 10);
7349         else
7350         HandleMachineMove(savedMessage, savedState);
7351 }
7352
7353 void
7354 HandleMachineMove(message, cps)
7355      char *message;
7356      ChessProgramState *cps;
7357 {
7358     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7359     char realname[MSG_SIZ];
7360     int fromX, fromY, toX, toY;
7361     ChessMove moveType;
7362     char promoChar;
7363     char *p;
7364     int machineWhite;
7365     char *bookHit;
7366
7367     cps->userError = 0;
7368
7369 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7370     /*
7371      * Kludge to ignore BEL characters
7372      */
7373     while (*message == '\007') message++;
7374
7375     /*
7376      * [HGM] engine debug message: ignore lines starting with '#' character
7377      */
7378     if(cps->debug && *message == '#') return;
7379
7380     /*
7381      * Look for book output
7382      */
7383     if (cps == &first && bookRequested) {
7384         if (message[0] == '\t' || message[0] == ' ') {
7385             /* Part of the book output is here; append it */
7386             strcat(bookOutput, message);
7387             strcat(bookOutput, "  \n");
7388             return;
7389         } else if (bookOutput[0] != NULLCHAR) {
7390             /* All of book output has arrived; display it */
7391             char *p = bookOutput;
7392             while (*p != NULLCHAR) {
7393                 if (*p == '\t') *p = ' ';
7394                 p++;
7395             }
7396             DisplayInformation(bookOutput);
7397             bookRequested = FALSE;
7398             /* Fall through to parse the current output */
7399         }
7400     }
7401
7402     /*
7403      * Look for machine move.
7404      */
7405     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7406         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7407     {
7408         /* This method is only useful on engines that support ping */
7409         if (cps->lastPing != cps->lastPong) {
7410           if (gameMode == BeginningOfGame) {
7411             /* Extra move from before last new; ignore */
7412             if (appData.debugMode) {
7413                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7414             }
7415           } else {
7416             if (appData.debugMode) {
7417                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7418                         cps->which, gameMode);
7419             }
7420
7421             SendToProgram("undo\n", cps);
7422           }
7423           return;
7424         }
7425
7426         switch (gameMode) {
7427           case BeginningOfGame:
7428             /* Extra move from before last reset; ignore */
7429             if (appData.debugMode) {
7430                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7431             }
7432             return;
7433
7434           case EndOfGame:
7435           case IcsIdle:
7436           default:
7437             /* Extra move after we tried to stop.  The mode test is
7438                not a reliable way of detecting this problem, but it's
7439                the best we can do on engines that don't support ping.
7440             */
7441             if (appData.debugMode) {
7442                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7443                         cps->which, gameMode);
7444             }
7445             SendToProgram("undo\n", cps);
7446             return;
7447
7448           case MachinePlaysWhite:
7449           case IcsPlayingWhite:
7450             machineWhite = TRUE;
7451             break;
7452
7453           case MachinePlaysBlack:
7454           case IcsPlayingBlack:
7455             machineWhite = FALSE;
7456             break;
7457
7458           case TwoMachinesPlay:
7459             machineWhite = (cps->twoMachinesColor[0] == 'w');
7460             break;
7461         }
7462         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7463             if (appData.debugMode) {
7464                 fprintf(debugFP,
7465                         "Ignoring move out of turn by %s, gameMode %d"
7466                         ", forwardMost %d\n",
7467                         cps->which, gameMode, forwardMostMove);
7468             }
7469             return;
7470         }
7471
7472     if (appData.debugMode) { int f = forwardMostMove;
7473         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7474                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7475                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7476     }
7477         if(cps->alphaRank) AlphaRank(machineMove, 4);
7478         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7479                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7480             /* Machine move could not be parsed; ignore it. */
7481           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7482                     machineMove, _(cps->which));
7483             DisplayError(buf1, 0);
7484             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7485                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7486             if (gameMode == TwoMachinesPlay) {
7487               GameEnds(machineWhite ? BlackWins : WhiteWins,
7488                        buf1, GE_XBOARD);
7489             }
7490             return;
7491         }
7492
7493         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7494         /* So we have to redo legality test with true e.p. status here,  */
7495         /* to make sure an illegal e.p. capture does not slip through,   */
7496         /* to cause a forfeit on a justified illegal-move complaint      */
7497         /* of the opponent.                                              */
7498         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7499            ChessMove moveType;
7500            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7501                              fromY, fromX, toY, toX, promoChar);
7502             if (appData.debugMode) {
7503                 int i;
7504                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7505                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7506                 fprintf(debugFP, "castling rights\n");
7507             }
7508             if(moveType == IllegalMove) {
7509               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7510                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7511                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7512                            buf1, GE_XBOARD);
7513                 return;
7514            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7515            /* [HGM] Kludge to handle engines that send FRC-style castling
7516               when they shouldn't (like TSCP-Gothic) */
7517            switch(moveType) {
7518              case WhiteASideCastleFR:
7519              case BlackASideCastleFR:
7520                toX+=2;
7521                currentMoveString[2]++;
7522                break;
7523              case WhiteHSideCastleFR:
7524              case BlackHSideCastleFR:
7525                toX--;
7526                currentMoveString[2]--;
7527                break;
7528              default: ; // nothing to do, but suppresses warning of pedantic compilers
7529            }
7530         }
7531         hintRequested = FALSE;
7532         lastHint[0] = NULLCHAR;
7533         bookRequested = FALSE;
7534         /* Program may be pondering now */
7535         cps->maybeThinking = TRUE;
7536         if (cps->sendTime == 2) cps->sendTime = 1;
7537         if (cps->offeredDraw) cps->offeredDraw--;
7538
7539         /* [AS] Save move info*/
7540         pvInfoList[ forwardMostMove ].score = programStats.score;
7541         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7542         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7543
7544         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7545
7546         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7547         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7548             int count = 0;
7549
7550             while( count < adjudicateLossPlies ) {
7551                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7552
7553                 if( count & 1 ) {
7554                     score = -score; /* Flip score for winning side */
7555                 }
7556
7557                 if( score > adjudicateLossThreshold ) {
7558                     break;
7559                 }
7560
7561                 count++;
7562             }
7563
7564             if( count >= adjudicateLossPlies ) {
7565                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7566
7567                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7568                     "Xboard adjudication",
7569                     GE_XBOARD );
7570
7571                 return;
7572             }
7573         }
7574
7575         if(Adjudicate(cps)) {
7576             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7577             return; // [HGM] adjudicate: for all automatic game ends
7578         }
7579
7580 #if ZIPPY
7581         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7582             first.initDone) {
7583           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7584                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7585                 SendToICS("draw ");
7586                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7587           }
7588           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7589           ics_user_moved = 1;
7590           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7591                 char buf[3*MSG_SIZ];
7592
7593                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7594                         programStats.score / 100.,
7595                         programStats.depth,
7596                         programStats.time / 100.,
7597                         (unsigned int)programStats.nodes,
7598                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7599                         programStats.movelist);
7600                 SendToICS(buf);
7601 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7602           }
7603         }
7604 #endif
7605
7606         /* [AS] Clear stats for next move */
7607         ClearProgramStats();
7608         thinkOutput[0] = NULLCHAR;
7609         hiddenThinkOutputState = 0;
7610
7611         bookHit = NULL;
7612         if (gameMode == TwoMachinesPlay) {
7613             /* [HGM] relaying draw offers moved to after reception of move */
7614             /* and interpreting offer as claim if it brings draw condition */
7615             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7616                 SendToProgram("draw\n", cps->other);
7617             }
7618             if (cps->other->sendTime) {
7619                 SendTimeRemaining(cps->other,
7620                                   cps->other->twoMachinesColor[0] == 'w');
7621             }
7622             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7623             if (firstMove && !bookHit) {
7624                 firstMove = FALSE;
7625                 if (cps->other->useColors) {
7626                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7627                 }
7628                 SendToProgram("go\n", cps->other);
7629             }
7630             cps->other->maybeThinking = TRUE;
7631         }
7632
7633         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7634
7635         if (!pausing && appData.ringBellAfterMoves) {
7636             RingBell();
7637         }
7638
7639         /*
7640          * Reenable menu items that were disabled while
7641          * machine was thinking
7642          */
7643         if (gameMode != TwoMachinesPlay)
7644             SetUserThinkingEnables();
7645
7646         // [HGM] book: after book hit opponent has received move and is now in force mode
7647         // force the book reply into it, and then fake that it outputted this move by jumping
7648         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7649         if(bookHit) {
7650                 static char bookMove[MSG_SIZ]; // a bit generous?
7651
7652                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7653                 strcat(bookMove, bookHit);
7654                 message = bookMove;
7655                 cps = cps->other;
7656                 programStats.nodes = programStats.depth = programStats.time =
7657                 programStats.score = programStats.got_only_move = 0;
7658                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7659
7660                 if(cps->lastPing != cps->lastPong) {
7661                     savedMessage = message; // args for deferred call
7662                     savedState = cps;
7663                     ScheduleDelayedEvent(DeferredBookMove, 10);
7664                     return;
7665                 }
7666                 goto FakeBookMove;
7667         }
7668
7669         return;
7670     }
7671
7672     /* Set special modes for chess engines.  Later something general
7673      *  could be added here; for now there is just one kludge feature,
7674      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7675      *  when "xboard" is given as an interactive command.
7676      */
7677     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7678         cps->useSigint = FALSE;
7679         cps->useSigterm = FALSE;
7680     }
7681     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7682       ParseFeatures(message+8, cps);
7683       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7684     }
7685
7686     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7687       int dummy, s=6; char buf[MSG_SIZ];
7688       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7689       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7690       ParseFEN(boards[0], &dummy, message+s);
7691       DrawPosition(TRUE, boards[0]);
7692       startedFromSetupPosition = TRUE;
7693       return;
7694     }
7695     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7696      * want this, I was asked to put it in, and obliged.
7697      */
7698     if (!strncmp(message, "setboard ", 9)) {
7699         Board initial_position;
7700
7701         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7702
7703         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7704             DisplayError(_("Bad FEN received from engine"), 0);
7705             return ;
7706         } else {
7707            Reset(TRUE, FALSE);
7708            CopyBoard(boards[0], initial_position);
7709            initialRulePlies = FENrulePlies;
7710            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7711            else gameMode = MachinePlaysBlack;
7712            DrawPosition(FALSE, boards[currentMove]);
7713         }
7714         return;
7715     }
7716
7717     /*
7718      * Look for communication commands
7719      */
7720     if (!strncmp(message, "telluser ", 9)) {
7721         if(message[9] == '\\' && message[10] == '\\')
7722             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7723         DisplayNote(message + 9);
7724         return;
7725     }
7726     if (!strncmp(message, "tellusererror ", 14)) {
7727         cps->userError = 1;
7728         if(message[14] == '\\' && message[15] == '\\')
7729             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7730         DisplayError(message + 14, 0);
7731         return;
7732     }
7733     if (!strncmp(message, "tellopponent ", 13)) {
7734       if (appData.icsActive) {
7735         if (loggedOn) {
7736           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7737           SendToICS(buf1);
7738         }
7739       } else {
7740         DisplayNote(message + 13);
7741       }
7742       return;
7743     }
7744     if (!strncmp(message, "tellothers ", 11)) {
7745       if (appData.icsActive) {
7746         if (loggedOn) {
7747           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7748           SendToICS(buf1);
7749         }
7750       }
7751       return;
7752     }
7753     if (!strncmp(message, "tellall ", 8)) {
7754       if (appData.icsActive) {
7755         if (loggedOn) {
7756           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7757           SendToICS(buf1);
7758         }
7759       } else {
7760         DisplayNote(message + 8);
7761       }
7762       return;
7763     }
7764     if (strncmp(message, "warning", 7) == 0) {
7765         /* Undocumented feature, use tellusererror in new code */
7766         DisplayError(message, 0);
7767         return;
7768     }
7769     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7770         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7771         strcat(realname, " query");
7772         AskQuestion(realname, buf2, buf1, cps->pr);
7773         return;
7774     }
7775     /* Commands from the engine directly to ICS.  We don't allow these to be
7776      *  sent until we are logged on. Crafty kibitzes have been known to
7777      *  interfere with the login process.
7778      */
7779     if (loggedOn) {
7780         if (!strncmp(message, "tellics ", 8)) {
7781             SendToICS(message + 8);
7782             SendToICS("\n");
7783             return;
7784         }
7785         if (!strncmp(message, "tellicsnoalias ", 15)) {
7786             SendToICS(ics_prefix);
7787             SendToICS(message + 15);
7788             SendToICS("\n");
7789             return;
7790         }
7791         /* The following are for backward compatibility only */
7792         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7793             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7794             SendToICS(ics_prefix);
7795             SendToICS(message);
7796             SendToICS("\n");
7797             return;
7798         }
7799     }
7800     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7801         return;
7802     }
7803     /*
7804      * If the move is illegal, cancel it and redraw the board.
7805      * Also deal with other error cases.  Matching is rather loose
7806      * here to accommodate engines written before the spec.
7807      */
7808     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7809         strncmp(message, "Error", 5) == 0) {
7810         if (StrStr(message, "name") ||
7811             StrStr(message, "rating") || StrStr(message, "?") ||
7812             StrStr(message, "result") || StrStr(message, "board") ||
7813             StrStr(message, "bk") || StrStr(message, "computer") ||
7814             StrStr(message, "variant") || StrStr(message, "hint") ||
7815             StrStr(message, "random") || StrStr(message, "depth") ||
7816             StrStr(message, "accepted")) {
7817             return;
7818         }
7819         if (StrStr(message, "protover")) {
7820           /* Program is responding to input, so it's apparently done
7821              initializing, and this error message indicates it is
7822              protocol version 1.  So we don't need to wait any longer
7823              for it to initialize and send feature commands. */
7824           FeatureDone(cps, 1);
7825           cps->protocolVersion = 1;
7826           return;
7827         }
7828         cps->maybeThinking = FALSE;
7829
7830         if (StrStr(message, "draw")) {
7831             /* Program doesn't have "draw" command */
7832             cps->sendDrawOffers = 0;
7833             return;
7834         }
7835         if (cps->sendTime != 1 &&
7836             (StrStr(message, "time") || StrStr(message, "otim"))) {
7837           /* Program apparently doesn't have "time" or "otim" command */
7838           cps->sendTime = 0;
7839           return;
7840         }
7841         if (StrStr(message, "analyze")) {
7842             cps->analysisSupport = FALSE;
7843             cps->analyzing = FALSE;
7844             Reset(FALSE, TRUE);
7845             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7846             DisplayError(buf2, 0);
7847             return;
7848         }
7849         if (StrStr(message, "(no matching move)st")) {
7850           /* Special kludge for GNU Chess 4 only */
7851           cps->stKludge = TRUE;
7852           SendTimeControl(cps, movesPerSession, timeControl,
7853                           timeIncrement, appData.searchDepth,
7854                           searchTime);
7855           return;
7856         }
7857         if (StrStr(message, "(no matching move)sd")) {
7858           /* Special kludge for GNU Chess 4 only */
7859           cps->sdKludge = TRUE;
7860           SendTimeControl(cps, movesPerSession, timeControl,
7861                           timeIncrement, appData.searchDepth,
7862                           searchTime);
7863           return;
7864         }
7865         if (!StrStr(message, "llegal")) {
7866             return;
7867         }
7868         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7869             gameMode == IcsIdle) return;
7870         if (forwardMostMove <= backwardMostMove) return;
7871         if (pausing) PauseEvent();
7872       if(appData.forceIllegal) {
7873             // [HGM] illegal: machine refused move; force position after move into it
7874           SendToProgram("force\n", cps);
7875           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7876                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7877                 // when black is to move, while there might be nothing on a2 or black
7878                 // might already have the move. So send the board as if white has the move.
7879                 // But first we must change the stm of the engine, as it refused the last move
7880                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7881                 if(WhiteOnMove(forwardMostMove)) {
7882                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7883                     SendBoard(cps, forwardMostMove); // kludgeless board
7884                 } else {
7885                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7886                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7887                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7888                 }
7889           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7890             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7891                  gameMode == TwoMachinesPlay)
7892               SendToProgram("go\n", cps);
7893             return;
7894       } else
7895         if (gameMode == PlayFromGameFile) {
7896             /* Stop reading this game file */
7897             gameMode = EditGame;
7898             ModeHighlight();
7899         }
7900         /* [HGM] illegal-move claim should forfeit game when Xboard */
7901         /* only passes fully legal moves                            */
7902         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7903             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7904                                 "False illegal-move claim", GE_XBOARD );
7905             return; // do not take back move we tested as valid
7906         }
7907         currentMove = forwardMostMove-1;
7908         DisplayMove(currentMove-1); /* before DisplayMoveError */
7909         SwitchClocks(forwardMostMove-1); // [HGM] race
7910         DisplayBothClocks();
7911         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7912                 parseList[currentMove], _(cps->which));
7913         DisplayMoveError(buf1);
7914         DrawPosition(FALSE, boards[currentMove]);
7915         return;
7916     }
7917     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7918         /* Program has a broken "time" command that
7919            outputs a string not ending in newline.
7920            Don't use it. */
7921         cps->sendTime = 0;
7922     }
7923
7924     /*
7925      * If chess program startup fails, exit with an error message.
7926      * Attempts to recover here are futile.
7927      */
7928     if ((StrStr(message, "unknown host") != NULL)
7929         || (StrStr(message, "No remote directory") != NULL)
7930         || (StrStr(message, "not found") != NULL)
7931         || (StrStr(message, "No such file") != NULL)
7932         || (StrStr(message, "can't alloc") != NULL)
7933         || (StrStr(message, "Permission denied") != NULL)) {
7934
7935         cps->maybeThinking = FALSE;
7936         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7937                 _(cps->which), cps->program, cps->host, message);
7938         RemoveInputSource(cps->isr);
7939         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7940             if(cps == &first) appData.noChessProgram = TRUE;
7941             DisplayError(buf1, 0);
7942         }
7943         return;
7944     }
7945
7946     /*
7947      * Look for hint output
7948      */
7949     if (sscanf(message, "Hint: %s", buf1) == 1) {
7950         if (cps == &first && hintRequested) {
7951             hintRequested = FALSE;
7952             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7953                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7954                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7955                                     PosFlags(forwardMostMove),
7956                                     fromY, fromX, toY, toX, promoChar, buf1);
7957                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7958                 DisplayInformation(buf2);
7959             } else {
7960                 /* Hint move could not be parsed!? */
7961               snprintf(buf2, sizeof(buf2),
7962                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7963                         buf1, _(cps->which));
7964                 DisplayError(buf2, 0);
7965             }
7966         } else {
7967           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7968         }
7969         return;
7970     }
7971
7972     /*
7973      * Ignore other messages if game is not in progress
7974      */
7975     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7976         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7977
7978     /*
7979      * look for win, lose, draw, or draw offer
7980      */
7981     if (strncmp(message, "1-0", 3) == 0) {
7982         char *p, *q, *r = "";
7983         p = strchr(message, '{');
7984         if (p) {
7985             q = strchr(p, '}');
7986             if (q) {
7987                 *q = NULLCHAR;
7988                 r = p + 1;
7989             }
7990         }
7991         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7992         return;
7993     } else if (strncmp(message, "0-1", 3) == 0) {
7994         char *p, *q, *r = "";
7995         p = strchr(message, '{');
7996         if (p) {
7997             q = strchr(p, '}');
7998             if (q) {
7999                 *q = NULLCHAR;
8000                 r = p + 1;
8001             }
8002         }
8003         /* Kludge for Arasan 4.1 bug */
8004         if (strcmp(r, "Black resigns") == 0) {
8005             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8006             return;
8007         }
8008         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8009         return;
8010     } else if (strncmp(message, "1/2", 3) == 0) {
8011         char *p, *q, *r = "";
8012         p = strchr(message, '{');
8013         if (p) {
8014             q = strchr(p, '}');
8015             if (q) {
8016                 *q = NULLCHAR;
8017                 r = p + 1;
8018             }
8019         }
8020
8021         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8022         return;
8023
8024     } else if (strncmp(message, "White resign", 12) == 0) {
8025         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8026         return;
8027     } else if (strncmp(message, "Black resign", 12) == 0) {
8028         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8029         return;
8030     } else if (strncmp(message, "White matches", 13) == 0 ||
8031                strncmp(message, "Black matches", 13) == 0   ) {
8032         /* [HGM] ignore GNUShogi noises */
8033         return;
8034     } else if (strncmp(message, "White", 5) == 0 &&
8035                message[5] != '(' &&
8036                StrStr(message, "Black") == NULL) {
8037         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8038         return;
8039     } else if (strncmp(message, "Black", 5) == 0 &&
8040                message[5] != '(') {
8041         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8042         return;
8043     } else if (strcmp(message, "resign") == 0 ||
8044                strcmp(message, "computer resigns") == 0) {
8045         switch (gameMode) {
8046           case MachinePlaysBlack:
8047           case IcsPlayingBlack:
8048             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8049             break;
8050           case MachinePlaysWhite:
8051           case IcsPlayingWhite:
8052             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8053             break;
8054           case TwoMachinesPlay:
8055             if (cps->twoMachinesColor[0] == 'w')
8056               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8057             else
8058               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8059             break;
8060           default:
8061             /* can't happen */
8062             break;
8063         }
8064         return;
8065     } else if (strncmp(message, "opponent mates", 14) == 0) {
8066         switch (gameMode) {
8067           case MachinePlaysBlack:
8068           case IcsPlayingBlack:
8069             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8070             break;
8071           case MachinePlaysWhite:
8072           case IcsPlayingWhite:
8073             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8074             break;
8075           case TwoMachinesPlay:
8076             if (cps->twoMachinesColor[0] == 'w')
8077               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8078             else
8079               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8080             break;
8081           default:
8082             /* can't happen */
8083             break;
8084         }
8085         return;
8086     } else if (strncmp(message, "computer mates", 14) == 0) {
8087         switch (gameMode) {
8088           case MachinePlaysBlack:
8089           case IcsPlayingBlack:
8090             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8091             break;
8092           case MachinePlaysWhite:
8093           case IcsPlayingWhite:
8094             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8095             break;
8096           case TwoMachinesPlay:
8097             if (cps->twoMachinesColor[0] == 'w')
8098               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8099             else
8100               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8101             break;
8102           default:
8103             /* can't happen */
8104             break;
8105         }
8106         return;
8107     } else if (strncmp(message, "checkmate", 9) == 0) {
8108         if (WhiteOnMove(forwardMostMove)) {
8109             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8110         } else {
8111             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8112         }
8113         return;
8114     } else if (strstr(message, "Draw") != NULL ||
8115                strstr(message, "game is a draw") != NULL) {
8116         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8117         return;
8118     } else if (strstr(message, "offer") != NULL &&
8119                strstr(message, "draw") != NULL) {
8120 #if ZIPPY
8121         if (appData.zippyPlay && first.initDone) {
8122             /* Relay offer to ICS */
8123             SendToICS(ics_prefix);
8124             SendToICS("draw\n");
8125         }
8126 #endif
8127         cps->offeredDraw = 2; /* valid until this engine moves twice */
8128         if (gameMode == TwoMachinesPlay) {
8129             if (cps->other->offeredDraw) {
8130                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8131             /* [HGM] in two-machine mode we delay relaying draw offer      */
8132             /* until after we also have move, to see if it is really claim */
8133             }
8134         } else if (gameMode == MachinePlaysWhite ||
8135                    gameMode == MachinePlaysBlack) {
8136           if (userOfferedDraw) {
8137             DisplayInformation(_("Machine accepts your draw offer"));
8138             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8139           } else {
8140             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8141           }
8142         }
8143     }
8144
8145
8146     /*
8147      * Look for thinking output
8148      */
8149     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8150           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8151                                 ) {
8152         int plylev, mvleft, mvtot, curscore, time;
8153         char mvname[MOVE_LEN];
8154         u64 nodes; // [DM]
8155         char plyext;
8156         int ignore = FALSE;
8157         int prefixHint = FALSE;
8158         mvname[0] = NULLCHAR;
8159
8160         switch (gameMode) {
8161           case MachinePlaysBlack:
8162           case IcsPlayingBlack:
8163             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8164             break;
8165           case MachinePlaysWhite:
8166           case IcsPlayingWhite:
8167             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8168             break;
8169           case AnalyzeMode:
8170           case AnalyzeFile:
8171             break;
8172           case IcsObserving: /* [DM] icsEngineAnalyze */
8173             if (!appData.icsEngineAnalyze) ignore = TRUE;
8174             break;
8175           case TwoMachinesPlay:
8176             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8177                 ignore = TRUE;
8178             }
8179             break;
8180           default:
8181             ignore = TRUE;
8182             break;
8183         }
8184
8185         if (!ignore) {
8186             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8187             buf1[0] = NULLCHAR;
8188             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8189                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8190
8191                 if (plyext != ' ' && plyext != '\t') {
8192                     time *= 100;
8193                 }
8194
8195                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8196                 if( cps->scoreIsAbsolute &&
8197                     ( gameMode == MachinePlaysBlack ||
8198                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8199                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8200                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8201                      !WhiteOnMove(currentMove)
8202                     ) )
8203                 {
8204                     curscore = -curscore;
8205                 }
8206
8207
8208                 tempStats.depth = plylev;
8209                 tempStats.nodes = nodes;
8210                 tempStats.time = time;
8211                 tempStats.score = curscore;
8212                 tempStats.got_only_move = 0;
8213
8214                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8215                         int ticklen;
8216
8217                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8218                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8219                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8220                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8221                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8222                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8223                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8224                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8225                 }
8226
8227                 /* Buffer overflow protection */
8228                 if (buf1[0] != NULLCHAR) {
8229                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8230                         && appData.debugMode) {
8231                         fprintf(debugFP,
8232                                 "PV is too long; using the first %u bytes.\n",
8233                                 (unsigned) sizeof(tempStats.movelist) - 1);
8234                     }
8235
8236                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8237                 } else {
8238                     sprintf(tempStats.movelist, " no PV\n");
8239                 }
8240
8241                 if (tempStats.seen_stat) {
8242                     tempStats.ok_to_send = 1;
8243                 }
8244
8245                 if (strchr(tempStats.movelist, '(') != NULL) {
8246                     tempStats.line_is_book = 1;
8247                     tempStats.nr_moves = 0;
8248                     tempStats.moves_left = 0;
8249                 } else {
8250                     tempStats.line_is_book = 0;
8251                 }
8252
8253                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8254                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8255
8256                 SendProgramStatsToFrontend( cps, &tempStats );
8257
8258                 /*
8259                     [AS] Protect the thinkOutput buffer from overflow... this
8260                     is only useful if buf1 hasn't overflowed first!
8261                 */
8262                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8263                          plylev,
8264                          (gameMode == TwoMachinesPlay ?
8265                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8266                          ((double) curscore) / 100.0,
8267                          prefixHint ? lastHint : "",
8268                          prefixHint ? " " : "" );
8269
8270                 if( buf1[0] != NULLCHAR ) {
8271                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8272
8273                     if( strlen(buf1) > max_len ) {
8274                         if( appData.debugMode) {
8275                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8276                         }
8277                         buf1[max_len+1] = '\0';
8278                     }
8279
8280                     strcat( thinkOutput, buf1 );
8281                 }
8282
8283                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8284                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8285                     DisplayMove(currentMove - 1);
8286                 }
8287                 return;
8288
8289             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8290                 /* crafty (9.25+) says "(only move) <move>"
8291                  * if there is only 1 legal move
8292                  */
8293                 sscanf(p, "(only move) %s", buf1);
8294                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8295                 sprintf(programStats.movelist, "%s (only move)", buf1);
8296                 programStats.depth = 1;
8297                 programStats.nr_moves = 1;
8298                 programStats.moves_left = 1;
8299                 programStats.nodes = 1;
8300                 programStats.time = 1;
8301                 programStats.got_only_move = 1;
8302
8303                 /* Not really, but we also use this member to
8304                    mean "line isn't going to change" (Crafty
8305                    isn't searching, so stats won't change) */
8306                 programStats.line_is_book = 1;
8307
8308                 SendProgramStatsToFrontend( cps, &programStats );
8309
8310                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8311                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8312                     DisplayMove(currentMove - 1);
8313                 }
8314                 return;
8315             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8316                               &time, &nodes, &plylev, &mvleft,
8317                               &mvtot, mvname) >= 5) {
8318                 /* The stat01: line is from Crafty (9.29+) in response
8319                    to the "." command */
8320                 programStats.seen_stat = 1;
8321                 cps->maybeThinking = TRUE;
8322
8323                 if (programStats.got_only_move || !appData.periodicUpdates)
8324                   return;
8325
8326                 programStats.depth = plylev;
8327                 programStats.time = time;
8328                 programStats.nodes = nodes;
8329                 programStats.moves_left = mvleft;
8330                 programStats.nr_moves = mvtot;
8331                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8332                 programStats.ok_to_send = 1;
8333                 programStats.movelist[0] = '\0';
8334
8335                 SendProgramStatsToFrontend( cps, &programStats );
8336
8337                 return;
8338
8339             } else if (strncmp(message,"++",2) == 0) {
8340                 /* Crafty 9.29+ outputs this */
8341                 programStats.got_fail = 2;
8342                 return;
8343
8344             } else if (strncmp(message,"--",2) == 0) {
8345                 /* Crafty 9.29+ outputs this */
8346                 programStats.got_fail = 1;
8347                 return;
8348
8349             } else if (thinkOutput[0] != NULLCHAR &&
8350                        strncmp(message, "    ", 4) == 0) {
8351                 unsigned message_len;
8352
8353                 p = message;
8354                 while (*p && *p == ' ') p++;
8355
8356                 message_len = strlen( p );
8357
8358                 /* [AS] Avoid buffer overflow */
8359                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8360                     strcat(thinkOutput, " ");
8361                     strcat(thinkOutput, p);
8362                 }
8363
8364                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8365                     strcat(programStats.movelist, " ");
8366                     strcat(programStats.movelist, p);
8367                 }
8368
8369                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8370                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8371                     DisplayMove(currentMove - 1);
8372                 }
8373                 return;
8374             }
8375         }
8376         else {
8377             buf1[0] = NULLCHAR;
8378
8379             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8380                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8381             {
8382                 ChessProgramStats cpstats;
8383
8384                 if (plyext != ' ' && plyext != '\t') {
8385                     time *= 100;
8386                 }
8387
8388                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8389                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8390                     curscore = -curscore;
8391                 }
8392
8393                 cpstats.depth = plylev;
8394                 cpstats.nodes = nodes;
8395                 cpstats.time = time;
8396                 cpstats.score = curscore;
8397                 cpstats.got_only_move = 0;
8398                 cpstats.movelist[0] = '\0';
8399
8400                 if (buf1[0] != NULLCHAR) {
8401                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8402                 }
8403
8404                 cpstats.ok_to_send = 0;
8405                 cpstats.line_is_book = 0;
8406                 cpstats.nr_moves = 0;
8407                 cpstats.moves_left = 0;
8408
8409                 SendProgramStatsToFrontend( cps, &cpstats );
8410             }
8411         }
8412     }
8413 }
8414
8415
8416 /* Parse a game score from the character string "game", and
8417    record it as the history of the current game.  The game
8418    score is NOT assumed to start from the standard position.
8419    The display is not updated in any way.
8420    */
8421 void
8422 ParseGameHistory(game)
8423      char *game;
8424 {
8425     ChessMove moveType;
8426     int fromX, fromY, toX, toY, boardIndex;
8427     char promoChar;
8428     char *p, *q;
8429     char buf[MSG_SIZ];
8430
8431     if (appData.debugMode)
8432       fprintf(debugFP, "Parsing game history: %s\n", game);
8433
8434     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8435     gameInfo.site = StrSave(appData.icsHost);
8436     gameInfo.date = PGNDate();
8437     gameInfo.round = StrSave("-");
8438
8439     /* Parse out names of players */
8440     while (*game == ' ') game++;
8441     p = buf;
8442     while (*game != ' ') *p++ = *game++;
8443     *p = NULLCHAR;
8444     gameInfo.white = StrSave(buf);
8445     while (*game == ' ') game++;
8446     p = buf;
8447     while (*game != ' ' && *game != '\n') *p++ = *game++;
8448     *p = NULLCHAR;
8449     gameInfo.black = StrSave(buf);
8450
8451     /* Parse moves */
8452     boardIndex = blackPlaysFirst ? 1 : 0;
8453     yynewstr(game);
8454     for (;;) {
8455         yyboardindex = boardIndex;
8456         moveType = (ChessMove) Myylex();
8457         switch (moveType) {
8458           case IllegalMove:             /* maybe suicide chess, etc. */
8459   if (appData.debugMode) {
8460     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8461     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8462     setbuf(debugFP, NULL);
8463   }
8464           case WhitePromotion:
8465           case BlackPromotion:
8466           case WhiteNonPromotion:
8467           case BlackNonPromotion:
8468           case NormalMove:
8469           case WhiteCapturesEnPassant:
8470           case BlackCapturesEnPassant:
8471           case WhiteKingSideCastle:
8472           case WhiteQueenSideCastle:
8473           case BlackKingSideCastle:
8474           case BlackQueenSideCastle:
8475           case WhiteKingSideCastleWild:
8476           case WhiteQueenSideCastleWild:
8477           case BlackKingSideCastleWild:
8478           case BlackQueenSideCastleWild:
8479           /* PUSH Fabien */
8480           case WhiteHSideCastleFR:
8481           case WhiteASideCastleFR:
8482           case BlackHSideCastleFR:
8483           case BlackASideCastleFR:
8484           /* POP Fabien */
8485             fromX = currentMoveString[0] - AAA;
8486             fromY = currentMoveString[1] - ONE;
8487             toX = currentMoveString[2] - AAA;
8488             toY = currentMoveString[3] - ONE;
8489             promoChar = currentMoveString[4];
8490             break;
8491           case WhiteDrop:
8492           case BlackDrop:
8493             fromX = moveType == WhiteDrop ?
8494               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8495             (int) CharToPiece(ToLower(currentMoveString[0]));
8496             fromY = DROP_RANK;
8497             toX = currentMoveString[2] - AAA;
8498             toY = currentMoveString[3] - ONE;
8499             promoChar = NULLCHAR;
8500             break;
8501           case AmbiguousMove:
8502             /* bug? */
8503             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8504   if (appData.debugMode) {
8505     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8506     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8507     setbuf(debugFP, NULL);
8508   }
8509             DisplayError(buf, 0);
8510             return;
8511           case ImpossibleMove:
8512             /* bug? */
8513             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8514   if (appData.debugMode) {
8515     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8516     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8517     setbuf(debugFP, NULL);
8518   }
8519             DisplayError(buf, 0);
8520             return;
8521           case EndOfFile:
8522             if (boardIndex < backwardMostMove) {
8523                 /* Oops, gap.  How did that happen? */
8524                 DisplayError(_("Gap in move list"), 0);
8525                 return;
8526             }
8527             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8528             if (boardIndex > forwardMostMove) {
8529                 forwardMostMove = boardIndex;
8530             }
8531             return;
8532           case ElapsedTime:
8533             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8534                 strcat(parseList[boardIndex-1], " ");
8535                 strcat(parseList[boardIndex-1], yy_text);
8536             }
8537             continue;
8538           case Comment:
8539           case PGNTag:
8540           case NAG:
8541           default:
8542             /* ignore */
8543             continue;
8544           case WhiteWins:
8545           case BlackWins:
8546           case GameIsDrawn:
8547           case GameUnfinished:
8548             if (gameMode == IcsExamining) {
8549                 if (boardIndex < backwardMostMove) {
8550                     /* Oops, gap.  How did that happen? */
8551                     return;
8552                 }
8553                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8554                 return;
8555             }
8556             gameInfo.result = moveType;
8557             p = strchr(yy_text, '{');
8558             if (p == NULL) p = strchr(yy_text, '(');
8559             if (p == NULL) {
8560                 p = yy_text;
8561                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8562             } else {
8563                 q = strchr(p, *p == '{' ? '}' : ')');
8564                 if (q != NULL) *q = NULLCHAR;
8565                 p++;
8566             }
8567             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8568             gameInfo.resultDetails = StrSave(p);
8569             continue;
8570         }
8571         if (boardIndex >= forwardMostMove &&
8572             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8573             backwardMostMove = blackPlaysFirst ? 1 : 0;
8574             return;
8575         }
8576         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8577                                  fromY, fromX, toY, toX, promoChar,
8578                                  parseList[boardIndex]);
8579         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8580         /* currentMoveString is set as a side-effect of yylex */
8581         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8582         strcat(moveList[boardIndex], "\n");
8583         boardIndex++;
8584         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8585         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8586           case MT_NONE:
8587           case MT_STALEMATE:
8588           default:
8589             break;
8590           case MT_CHECK:
8591             if(gameInfo.variant != VariantShogi)
8592                 strcat(parseList[boardIndex - 1], "+");
8593             break;
8594           case MT_CHECKMATE:
8595           case MT_STAINMATE:
8596             strcat(parseList[boardIndex - 1], "#");
8597             break;
8598         }
8599     }
8600 }
8601
8602
8603 /* Apply a move to the given board  */
8604 void
8605 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8606      int fromX, fromY, toX, toY;
8607      int promoChar;
8608      Board board;
8609 {
8610   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8611   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8612
8613     /* [HGM] compute & store e.p. status and castling rights for new position */
8614     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8615
8616       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8617       oldEP = (signed char)board[EP_STATUS];
8618       board[EP_STATUS] = EP_NONE;
8619
8620       if( board[toY][toX] != EmptySquare )
8621            board[EP_STATUS] = EP_CAPTURE;
8622
8623   if (fromY == DROP_RANK) {
8624         /* must be first */
8625         piece = board[toY][toX] = (ChessSquare) fromX;
8626   } else {
8627       int i;
8628
8629       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8630            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8631                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8632       } else
8633       if( board[fromY][fromX] == WhitePawn ) {
8634            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8635                board[EP_STATUS] = EP_PAWN_MOVE;
8636            if( toY-fromY==2) {
8637                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8638                         gameInfo.variant != VariantBerolina || toX < fromX)
8639                       board[EP_STATUS] = toX | berolina;
8640                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8641                         gameInfo.variant != VariantBerolina || toX > fromX)
8642                       board[EP_STATUS] = toX;
8643            }
8644       } else
8645       if( board[fromY][fromX] == BlackPawn ) {
8646            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8647                board[EP_STATUS] = EP_PAWN_MOVE;
8648            if( toY-fromY== -2) {
8649                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8650                         gameInfo.variant != VariantBerolina || toX < fromX)
8651                       board[EP_STATUS] = toX | berolina;
8652                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8653                         gameInfo.variant != VariantBerolina || toX > fromX)
8654                       board[EP_STATUS] = toX;
8655            }
8656        }
8657
8658        for(i=0; i<nrCastlingRights; i++) {
8659            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8660               board[CASTLING][i] == toX   && castlingRank[i] == toY
8661              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8662        }
8663
8664      if (fromX == toX && fromY == toY) return;
8665
8666      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8667      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8668      if(gameInfo.variant == VariantKnightmate)
8669          king += (int) WhiteUnicorn - (int) WhiteKing;
8670
8671     /* Code added by Tord: */
8672     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8673     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8674         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8675       board[fromY][fromX] = EmptySquare;
8676       board[toY][toX] = EmptySquare;
8677       if((toX > fromX) != (piece == WhiteRook)) {
8678         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8679       } else {
8680         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8681       }
8682     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8683                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8684       board[fromY][fromX] = EmptySquare;
8685       board[toY][toX] = EmptySquare;
8686       if((toX > fromX) != (piece == BlackRook)) {
8687         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8688       } else {
8689         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8690       }
8691     /* End of code added by Tord */
8692
8693     } else if (board[fromY][fromX] == king
8694         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8695         && toY == fromY && toX > fromX+1) {
8696         board[fromY][fromX] = EmptySquare;
8697         board[toY][toX] = king;
8698         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8699         board[fromY][BOARD_RGHT-1] = EmptySquare;
8700     } else if (board[fromY][fromX] == king
8701         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8702                && toY == fromY && toX < fromX-1) {
8703         board[fromY][fromX] = EmptySquare;
8704         board[toY][toX] = king;
8705         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8706         board[fromY][BOARD_LEFT] = EmptySquare;
8707     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8708                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8709                && toY >= BOARD_HEIGHT-promoRank
8710                ) {
8711         /* white pawn promotion */
8712         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8713         if (board[toY][toX] == EmptySquare) {
8714             board[toY][toX] = WhiteQueen;
8715         }
8716         if(gameInfo.variant==VariantBughouse ||
8717            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8718             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8719         board[fromY][fromX] = EmptySquare;
8720     } else if ((fromY == BOARD_HEIGHT-4)
8721                && (toX != fromX)
8722                && gameInfo.variant != VariantXiangqi
8723                && gameInfo.variant != VariantBerolina
8724                && (board[fromY][fromX] == WhitePawn)
8725                && (board[toY][toX] == EmptySquare)) {
8726         board[fromY][fromX] = EmptySquare;
8727         board[toY][toX] = WhitePawn;
8728         captured = board[toY - 1][toX];
8729         board[toY - 1][toX] = EmptySquare;
8730     } else if ((fromY == BOARD_HEIGHT-4)
8731                && (toX == fromX)
8732                && gameInfo.variant == VariantBerolina
8733                && (board[fromY][fromX] == WhitePawn)
8734                && (board[toY][toX] == EmptySquare)) {
8735         board[fromY][fromX] = EmptySquare;
8736         board[toY][toX] = WhitePawn;
8737         if(oldEP & EP_BEROLIN_A) {
8738                 captured = board[fromY][fromX-1];
8739                 board[fromY][fromX-1] = EmptySquare;
8740         }else{  captured = board[fromY][fromX+1];
8741                 board[fromY][fromX+1] = EmptySquare;
8742         }
8743     } else if (board[fromY][fromX] == king
8744         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8745                && toY == fromY && toX > fromX+1) {
8746         board[fromY][fromX] = EmptySquare;
8747         board[toY][toX] = king;
8748         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8749         board[fromY][BOARD_RGHT-1] = EmptySquare;
8750     } else if (board[fromY][fromX] == king
8751         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8752                && toY == fromY && toX < fromX-1) {
8753         board[fromY][fromX] = EmptySquare;
8754         board[toY][toX] = king;
8755         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8756         board[fromY][BOARD_LEFT] = EmptySquare;
8757     } else if (fromY == 7 && fromX == 3
8758                && board[fromY][fromX] == BlackKing
8759                && toY == 7 && toX == 5) {
8760         board[fromY][fromX] = EmptySquare;
8761         board[toY][toX] = BlackKing;
8762         board[fromY][7] = EmptySquare;
8763         board[toY][4] = BlackRook;
8764     } else if (fromY == 7 && fromX == 3
8765                && board[fromY][fromX] == BlackKing
8766                && toY == 7 && toX == 1) {
8767         board[fromY][fromX] = EmptySquare;
8768         board[toY][toX] = BlackKing;
8769         board[fromY][0] = EmptySquare;
8770         board[toY][2] = BlackRook;
8771     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8772                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8773                && toY < promoRank
8774                ) {
8775         /* black pawn promotion */
8776         board[toY][toX] = CharToPiece(ToLower(promoChar));
8777         if (board[toY][toX] == EmptySquare) {
8778             board[toY][toX] = BlackQueen;
8779         }
8780         if(gameInfo.variant==VariantBughouse ||
8781            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8782             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8783         board[fromY][fromX] = EmptySquare;
8784     } else if ((fromY == 3)
8785                && (toX != fromX)
8786                && gameInfo.variant != VariantXiangqi
8787                && gameInfo.variant != VariantBerolina
8788                && (board[fromY][fromX] == BlackPawn)
8789                && (board[toY][toX] == EmptySquare)) {
8790         board[fromY][fromX] = EmptySquare;
8791         board[toY][toX] = BlackPawn;
8792         captured = board[toY + 1][toX];
8793         board[toY + 1][toX] = EmptySquare;
8794     } else if ((fromY == 3)
8795                && (toX == fromX)
8796                && gameInfo.variant == VariantBerolina
8797                && (board[fromY][fromX] == BlackPawn)
8798                && (board[toY][toX] == EmptySquare)) {
8799         board[fromY][fromX] = EmptySquare;
8800         board[toY][toX] = BlackPawn;
8801         if(oldEP & EP_BEROLIN_A) {
8802                 captured = board[fromY][fromX-1];
8803                 board[fromY][fromX-1] = EmptySquare;
8804         }else{  captured = board[fromY][fromX+1];
8805                 board[fromY][fromX+1] = EmptySquare;
8806         }
8807     } else {
8808         board[toY][toX] = board[fromY][fromX];
8809         board[fromY][fromX] = EmptySquare;
8810     }
8811   }
8812
8813     if (gameInfo.holdingsWidth != 0) {
8814
8815       /* !!A lot more code needs to be written to support holdings  */
8816       /* [HGM] OK, so I have written it. Holdings are stored in the */
8817       /* penultimate board files, so they are automaticlly stored   */
8818       /* in the game history.                                       */
8819       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8820                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8821         /* Delete from holdings, by decreasing count */
8822         /* and erasing image if necessary            */
8823         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8824         if(p < (int) BlackPawn) { /* white drop */
8825              p -= (int)WhitePawn;
8826                  p = PieceToNumber((ChessSquare)p);
8827              if(p >= gameInfo.holdingsSize) p = 0;
8828              if(--board[p][BOARD_WIDTH-2] <= 0)
8829                   board[p][BOARD_WIDTH-1] = EmptySquare;
8830              if((int)board[p][BOARD_WIDTH-2] < 0)
8831                         board[p][BOARD_WIDTH-2] = 0;
8832         } else {                  /* black drop */
8833              p -= (int)BlackPawn;
8834                  p = PieceToNumber((ChessSquare)p);
8835              if(p >= gameInfo.holdingsSize) p = 0;
8836              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8837                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8838              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8839                         board[BOARD_HEIGHT-1-p][1] = 0;
8840         }
8841       }
8842       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8843           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8844         /* [HGM] holdings: Add to holdings, if holdings exist */
8845         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8846                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8847                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8848         }
8849         p = (int) captured;
8850         if (p >= (int) BlackPawn) {
8851           p -= (int)BlackPawn;
8852           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8853                   /* in Shogi restore piece to its original  first */
8854                   captured = (ChessSquare) (DEMOTED captured);
8855                   p = DEMOTED p;
8856           }
8857           p = PieceToNumber((ChessSquare)p);
8858           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8859           board[p][BOARD_WIDTH-2]++;
8860           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8861         } else {
8862           p -= (int)WhitePawn;
8863           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8864                   captured = (ChessSquare) (DEMOTED captured);
8865                   p = DEMOTED p;
8866           }
8867           p = PieceToNumber((ChessSquare)p);
8868           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8869           board[BOARD_HEIGHT-1-p][1]++;
8870           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8871         }
8872       }
8873     } else if (gameInfo.variant == VariantAtomic) {
8874       if (captured != EmptySquare) {
8875         int y, x;
8876         for (y = toY-1; y <= toY+1; y++) {
8877           for (x = toX-1; x <= toX+1; x++) {
8878             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8879                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8880               board[y][x] = EmptySquare;
8881             }
8882           }
8883         }
8884         board[toY][toX] = EmptySquare;
8885       }
8886     }
8887     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8888         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8889     } else
8890     if(promoChar == '+') {
8891         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8892         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8893     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8894         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8895     }
8896     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8897                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8898         // [HGM] superchess: take promotion piece out of holdings
8899         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8900         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8901             if(!--board[k][BOARD_WIDTH-2])
8902                 board[k][BOARD_WIDTH-1] = EmptySquare;
8903         } else {
8904             if(!--board[BOARD_HEIGHT-1-k][1])
8905                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8906         }
8907     }
8908
8909 }
8910
8911 /* Updates forwardMostMove */
8912 void
8913 MakeMove(fromX, fromY, toX, toY, promoChar)
8914      int fromX, fromY, toX, toY;
8915      int promoChar;
8916 {
8917 //    forwardMostMove++; // [HGM] bare: moved downstream
8918
8919     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8920         int timeLeft; static int lastLoadFlag=0; int king, piece;
8921         piece = boards[forwardMostMove][fromY][fromX];
8922         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8923         if(gameInfo.variant == VariantKnightmate)
8924             king += (int) WhiteUnicorn - (int) WhiteKing;
8925         if(forwardMostMove == 0) {
8926             if(blackPlaysFirst)
8927                 fprintf(serverMoves, "%s;", second.tidy);
8928             fprintf(serverMoves, "%s;", first.tidy);
8929             if(!blackPlaysFirst)
8930                 fprintf(serverMoves, "%s;", second.tidy);
8931         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8932         lastLoadFlag = loadFlag;
8933         // print base move
8934         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8935         // print castling suffix
8936         if( toY == fromY && piece == king ) {
8937             if(toX-fromX > 1)
8938                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8939             if(fromX-toX >1)
8940                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8941         }
8942         // e.p. suffix
8943         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8944              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8945              boards[forwardMostMove][toY][toX] == EmptySquare
8946              && fromX != toX && fromY != toY)
8947                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8948         // promotion suffix
8949         if(promoChar != NULLCHAR)
8950                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8951         if(!loadFlag) {
8952             fprintf(serverMoves, "/%d/%d",
8953                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8954             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8955             else                      timeLeft = blackTimeRemaining/1000;
8956             fprintf(serverMoves, "/%d", timeLeft);
8957         }
8958         fflush(serverMoves);
8959     }
8960
8961     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8962       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8963                         0, 1);
8964       return;
8965     }
8966     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8967     if (commentList[forwardMostMove+1] != NULL) {
8968         free(commentList[forwardMostMove+1]);
8969         commentList[forwardMostMove+1] = NULL;
8970     }
8971     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8972     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8973     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8974     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8975     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8976     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8977     gameInfo.result = GameUnfinished;
8978     if (gameInfo.resultDetails != NULL) {
8979         free(gameInfo.resultDetails);
8980         gameInfo.resultDetails = NULL;
8981     }
8982     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8983                               moveList[forwardMostMove - 1]);
8984     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8985                              PosFlags(forwardMostMove - 1),
8986                              fromY, fromX, toY, toX, promoChar,
8987                              parseList[forwardMostMove - 1]);
8988     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8989       case MT_NONE:
8990       case MT_STALEMATE:
8991       default:
8992         break;
8993       case MT_CHECK:
8994         if(gameInfo.variant != VariantShogi)
8995             strcat(parseList[forwardMostMove - 1], "+");
8996         break;
8997       case MT_CHECKMATE:
8998       case MT_STAINMATE:
8999         strcat(parseList[forwardMostMove - 1], "#");
9000         break;
9001     }
9002     if (appData.debugMode) {
9003         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9004     }
9005
9006 }
9007
9008 /* Updates currentMove if not pausing */
9009 void
9010 ShowMove(fromX, fromY, toX, toY)
9011 {
9012     int instant = (gameMode == PlayFromGameFile) ?
9013         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9014     if(appData.noGUI) return;
9015     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9016         if (!instant) {
9017             if (forwardMostMove == currentMove + 1) {
9018                 AnimateMove(boards[forwardMostMove - 1],
9019                             fromX, fromY, toX, toY);
9020             }
9021             if (appData.highlightLastMove) {
9022                 SetHighlights(fromX, fromY, toX, toY);
9023             }
9024         }
9025         currentMove = forwardMostMove;
9026     }
9027
9028     if (instant) return;
9029
9030     DisplayMove(currentMove - 1);
9031     DrawPosition(FALSE, boards[currentMove]);
9032     DisplayBothClocks();
9033     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9034 }
9035
9036 void SendEgtPath(ChessProgramState *cps)
9037 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9038         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9039
9040         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9041
9042         while(*p) {
9043             char c, *q = name+1, *r, *s;
9044
9045             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9046             while(*p && *p != ',') *q++ = *p++;
9047             *q++ = ':'; *q = 0;
9048             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9049                 strcmp(name, ",nalimov:") == 0 ) {
9050                 // take nalimov path from the menu-changeable option first, if it is defined
9051               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9052                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9053             } else
9054             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9055                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9056                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9057                 s = r = StrStr(s, ":") + 1; // beginning of path info
9058                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9059                 c = *r; *r = 0;             // temporarily null-terminate path info
9060                     *--q = 0;               // strip of trailig ':' from name
9061                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9062                 *r = c;
9063                 SendToProgram(buf,cps);     // send egtbpath command for this format
9064             }
9065             if(*p == ',') p++; // read away comma to position for next format name
9066         }
9067 }
9068
9069 void
9070 InitChessProgram(cps, setup)
9071      ChessProgramState *cps;
9072      int setup; /* [HGM] needed to setup FRC opening position */
9073 {
9074     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9075     if (appData.noChessProgram) return;
9076     hintRequested = FALSE;
9077     bookRequested = FALSE;
9078
9079     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9080     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9081     if(cps->memSize) { /* [HGM] memory */
9082       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9083         SendToProgram(buf, cps);
9084     }
9085     SendEgtPath(cps); /* [HGM] EGT */
9086     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9087       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9088         SendToProgram(buf, cps);
9089     }
9090
9091     SendToProgram(cps->initString, cps);
9092     if (gameInfo.variant != VariantNormal &&
9093         gameInfo.variant != VariantLoadable
9094         /* [HGM] also send variant if board size non-standard */
9095         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9096                                             ) {
9097       char *v = VariantName(gameInfo.variant);
9098       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9099         /* [HGM] in protocol 1 we have to assume all variants valid */
9100         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9101         DisplayFatalError(buf, 0, 1);
9102         return;
9103       }
9104
9105       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9106       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9107       if( gameInfo.variant == VariantXiangqi )
9108            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9109       if( gameInfo.variant == VariantShogi )
9110            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9111       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9112            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9113       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9114           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9115            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9116       if( gameInfo.variant == VariantCourier )
9117            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9118       if( gameInfo.variant == VariantSuper )
9119            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9120       if( gameInfo.variant == VariantGreat )
9121            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9122       if( gameInfo.variant == VariantSChess )
9123            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9124
9125       if(overruled) {
9126         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9127                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9128            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9129            if(StrStr(cps->variants, b) == NULL) {
9130                // specific sized variant not known, check if general sizing allowed
9131                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9132                    if(StrStr(cps->variants, "boardsize") == NULL) {
9133                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9134                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9135                        DisplayFatalError(buf, 0, 1);
9136                        return;
9137                    }
9138                    /* [HGM] here we really should compare with the maximum supported board size */
9139                }
9140            }
9141       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9142       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9143       SendToProgram(buf, cps);
9144     }
9145     currentlyInitializedVariant = gameInfo.variant;
9146
9147     /* [HGM] send opening position in FRC to first engine */
9148     if(setup) {
9149           SendToProgram("force\n", cps);
9150           SendBoard(cps, 0);
9151           /* engine is now in force mode! Set flag to wake it up after first move. */
9152           setboardSpoiledMachineBlack = 1;
9153     }
9154
9155     if (cps->sendICS) {
9156       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9157       SendToProgram(buf, cps);
9158     }
9159     cps->maybeThinking = FALSE;
9160     cps->offeredDraw = 0;
9161     if (!appData.icsActive) {
9162         SendTimeControl(cps, movesPerSession, timeControl,
9163                         timeIncrement, appData.searchDepth,
9164                         searchTime);
9165     }
9166     if (appData.showThinking
9167         // [HGM] thinking: four options require thinking output to be sent
9168         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9169                                 ) {
9170         SendToProgram("post\n", cps);
9171     }
9172     SendToProgram("hard\n", cps);
9173     if (!appData.ponderNextMove) {
9174         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9175            it without being sure what state we are in first.  "hard"
9176            is not a toggle, so that one is OK.
9177          */
9178         SendToProgram("easy\n", cps);
9179     }
9180     if (cps->usePing) {
9181       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9182       SendToProgram(buf, cps);
9183     }
9184     cps->initDone = TRUE;
9185 }
9186
9187
9188 void
9189 StartChessProgram(cps)
9190      ChessProgramState *cps;
9191 {
9192     char buf[MSG_SIZ];
9193     int err;
9194
9195     if (appData.noChessProgram) return;
9196     cps->initDone = FALSE;
9197
9198     if (strcmp(cps->host, "localhost") == 0) {
9199         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9200     } else if (*appData.remoteShell == NULLCHAR) {
9201         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9202     } else {
9203         if (*appData.remoteUser == NULLCHAR) {
9204           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9205                     cps->program);
9206         } else {
9207           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9208                     cps->host, appData.remoteUser, cps->program);
9209         }
9210         err = StartChildProcess(buf, "", &cps->pr);
9211     }
9212
9213     if (err != 0) {
9214       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9215         DisplayFatalError(buf, err, 1);
9216         cps->pr = NoProc;
9217         cps->isr = NULL;
9218         return;
9219     }
9220
9221     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9222     if (cps->protocolVersion > 1) {
9223       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9224       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9225       cps->comboCnt = 0;  //                and values of combo boxes
9226       SendToProgram(buf, cps);
9227     } else {
9228       SendToProgram("xboard\n", cps);
9229     }
9230 }
9231
9232
9233 void
9234 TwoMachinesEventIfReady P((void))
9235 {
9236   if (first.lastPing != first.lastPong) {
9237     DisplayMessage("", _("Waiting for first chess program"));
9238     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9239     return;
9240   }
9241   if (second.lastPing != second.lastPong) {
9242     DisplayMessage("", _("Waiting for second chess program"));
9243     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9244     return;
9245   }
9246   ThawUI();
9247   TwoMachinesEvent();
9248 }
9249
9250 void
9251 NextMatchGame P((void))
9252 {
9253     int index; /* [HGM] autoinc: step load index during match */
9254     Reset(FALSE, TRUE);
9255     if (*appData.loadGameFile != NULLCHAR) {
9256         index = appData.loadGameIndex;
9257         if(index < 0) { // [HGM] autoinc
9258             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9259             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9260         }
9261         LoadGameFromFile(appData.loadGameFile,
9262                          index,
9263                          appData.loadGameFile, FALSE);
9264     } else if (*appData.loadPositionFile != NULLCHAR) {
9265         index = appData.loadPositionIndex;
9266         if(index < 0) { // [HGM] autoinc
9267             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9268             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9269         }
9270         LoadPositionFromFile(appData.loadPositionFile,
9271                              index,
9272                              appData.loadPositionFile);
9273     }
9274     TwoMachinesEventIfReady();
9275 }
9276
9277 void UserAdjudicationEvent( int result )
9278 {
9279     ChessMove gameResult = GameIsDrawn;
9280
9281     if( result > 0 ) {
9282         gameResult = WhiteWins;
9283     }
9284     else if( result < 0 ) {
9285         gameResult = BlackWins;
9286     }
9287
9288     if( gameMode == TwoMachinesPlay ) {
9289         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9290     }
9291 }
9292
9293
9294 // [HGM] save: calculate checksum of game to make games easily identifiable
9295 int StringCheckSum(char *s)
9296 {
9297         int i = 0;
9298         if(s==NULL) return 0;
9299         while(*s) i = i*259 + *s++;
9300         return i;
9301 }
9302
9303 int GameCheckSum()
9304 {
9305         int i, sum=0;
9306         for(i=backwardMostMove; i<forwardMostMove; i++) {
9307                 sum += pvInfoList[i].depth;
9308                 sum += StringCheckSum(parseList[i]);
9309                 sum += StringCheckSum(commentList[i]);
9310                 sum *= 261;
9311         }
9312         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9313         return sum + StringCheckSum(commentList[i]);
9314 } // end of save patch
9315
9316 void
9317 GameEnds(result, resultDetails, whosays)
9318      ChessMove result;
9319      char *resultDetails;
9320      int whosays;
9321 {
9322     GameMode nextGameMode;
9323     int isIcsGame;
9324     char buf[MSG_SIZ], popupRequested = 0;
9325
9326     if(endingGame) return; /* [HGM] crash: forbid recursion */
9327     endingGame = 1;
9328     if(twoBoards) { // [HGM] dual: switch back to one board
9329         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9330         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9331     }
9332     if (appData.debugMode) {
9333       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9334               result, resultDetails ? resultDetails : "(null)", whosays);
9335     }
9336
9337     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9338
9339     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9340         /* If we are playing on ICS, the server decides when the
9341            game is over, but the engine can offer to draw, claim
9342            a draw, or resign.
9343          */
9344 #if ZIPPY
9345         if (appData.zippyPlay && first.initDone) {
9346             if (result == GameIsDrawn) {
9347                 /* In case draw still needs to be claimed */
9348                 SendToICS(ics_prefix);
9349                 SendToICS("draw\n");
9350             } else if (StrCaseStr(resultDetails, "resign")) {
9351                 SendToICS(ics_prefix);
9352                 SendToICS("resign\n");
9353             }
9354         }
9355 #endif
9356         endingGame = 0; /* [HGM] crash */
9357         return;
9358     }
9359
9360     /* If we're loading the game from a file, stop */
9361     if (whosays == GE_FILE) {
9362       (void) StopLoadGameTimer();
9363       gameFileFP = NULL;
9364     }
9365
9366     /* Cancel draw offers */
9367     first.offeredDraw = second.offeredDraw = 0;
9368
9369     /* If this is an ICS game, only ICS can really say it's done;
9370        if not, anyone can. */
9371     isIcsGame = (gameMode == IcsPlayingWhite ||
9372                  gameMode == IcsPlayingBlack ||
9373                  gameMode == IcsObserving    ||
9374                  gameMode == IcsExamining);
9375
9376     if (!isIcsGame || whosays == GE_ICS) {
9377         /* OK -- not an ICS game, or ICS said it was done */
9378         StopClocks();
9379         if (!isIcsGame && !appData.noChessProgram)
9380           SetUserThinkingEnables();
9381
9382         /* [HGM] if a machine claims the game end we verify this claim */
9383         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9384             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9385                 char claimer;
9386                 ChessMove trueResult = (ChessMove) -1;
9387
9388                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9389                                             first.twoMachinesColor[0] :
9390                                             second.twoMachinesColor[0] ;
9391
9392                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9393                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9394                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9395                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9396                 } else
9397                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9398                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9399                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9400                 } else
9401                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9402                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9403                 }
9404
9405                 // now verify win claims, but not in drop games, as we don't understand those yet
9406                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9407                                                  || gameInfo.variant == VariantGreat) &&
9408                     (result == WhiteWins && claimer == 'w' ||
9409                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9410                       if (appData.debugMode) {
9411                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9412                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9413                       }
9414                       if(result != trueResult) {
9415                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9416                               result = claimer == 'w' ? BlackWins : WhiteWins;
9417                               resultDetails = buf;
9418                       }
9419                 } else
9420                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9421                     && (forwardMostMove <= backwardMostMove ||
9422                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9423                         (claimer=='b')==(forwardMostMove&1))
9424                                                                                   ) {
9425                       /* [HGM] verify: draws that were not flagged are false claims */
9426                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9427                       result = claimer == 'w' ? BlackWins : WhiteWins;
9428                       resultDetails = buf;
9429                 }
9430                 /* (Claiming a loss is accepted no questions asked!) */
9431             }
9432             /* [HGM] bare: don't allow bare King to win */
9433             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9434                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9435                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9436                && result != GameIsDrawn)
9437             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9438                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9439                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9440                         if(p >= 0 && p <= (int)WhiteKing) k++;
9441                 }
9442                 if (appData.debugMode) {
9443                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9444                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9445                 }
9446                 if(k <= 1) {
9447                         result = GameIsDrawn;
9448                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9449                         resultDetails = buf;
9450                 }
9451             }
9452         }
9453
9454
9455         if(serverMoves != NULL && !loadFlag) { char c = '=';
9456             if(result==WhiteWins) c = '+';
9457             if(result==BlackWins) c = '-';
9458             if(resultDetails != NULL)
9459                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9460         }
9461         if (resultDetails != NULL) {
9462             gameInfo.result = result;
9463             gameInfo.resultDetails = StrSave(resultDetails);
9464
9465             /* display last move only if game was not loaded from file */
9466             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9467                 DisplayMove(currentMove - 1);
9468
9469             if (forwardMostMove != 0) {
9470                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9471                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9472                                                                 ) {
9473                     if (*appData.saveGameFile != NULLCHAR) {
9474                         SaveGameToFile(appData.saveGameFile, TRUE);
9475                     } else if (appData.autoSaveGames) {
9476                         AutoSaveGame();
9477                     }
9478                     if (*appData.savePositionFile != NULLCHAR) {
9479                         SavePositionToFile(appData.savePositionFile);
9480                     }
9481                 }
9482             }
9483
9484             /* Tell program how game ended in case it is learning */
9485             /* [HGM] Moved this to after saving the PGN, just in case */
9486             /* engine died and we got here through time loss. In that */
9487             /* case we will get a fatal error writing the pipe, which */
9488             /* would otherwise lose us the PGN.                       */
9489             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9490             /* output during GameEnds should never be fatal anymore   */
9491             if (gameMode == MachinePlaysWhite ||
9492                 gameMode == MachinePlaysBlack ||
9493                 gameMode == TwoMachinesPlay ||
9494                 gameMode == IcsPlayingWhite ||
9495                 gameMode == IcsPlayingBlack ||
9496                 gameMode == BeginningOfGame) {
9497                 char buf[MSG_SIZ];
9498                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9499                         resultDetails);
9500                 if (first.pr != NoProc) {
9501                     SendToProgram(buf, &first);
9502                 }
9503                 if (second.pr != NoProc &&
9504                     gameMode == TwoMachinesPlay) {
9505                     SendToProgram(buf, &second);
9506                 }
9507             }
9508         }
9509
9510         if (appData.icsActive) {
9511             if (appData.quietPlay &&
9512                 (gameMode == IcsPlayingWhite ||
9513                  gameMode == IcsPlayingBlack)) {
9514                 SendToICS(ics_prefix);
9515                 SendToICS("set shout 1\n");
9516             }
9517             nextGameMode = IcsIdle;
9518             ics_user_moved = FALSE;
9519             /* clean up premove.  It's ugly when the game has ended and the
9520              * premove highlights are still on the board.
9521              */
9522             if (gotPremove) {
9523               gotPremove = FALSE;
9524               ClearPremoveHighlights();
9525               DrawPosition(FALSE, boards[currentMove]);
9526             }
9527             if (whosays == GE_ICS) {
9528                 switch (result) {
9529                 case WhiteWins:
9530                     if (gameMode == IcsPlayingWhite)
9531                         PlayIcsWinSound();
9532                     else if(gameMode == IcsPlayingBlack)
9533                         PlayIcsLossSound();
9534                     break;
9535                 case BlackWins:
9536                     if (gameMode == IcsPlayingBlack)
9537                         PlayIcsWinSound();
9538                     else if(gameMode == IcsPlayingWhite)
9539                         PlayIcsLossSound();
9540                     break;
9541                 case GameIsDrawn:
9542                     PlayIcsDrawSound();
9543                     break;
9544                 default:
9545                     PlayIcsUnfinishedSound();
9546                 }
9547             }
9548         } else if (gameMode == EditGame ||
9549                    gameMode == PlayFromGameFile ||
9550                    gameMode == AnalyzeMode ||
9551                    gameMode == AnalyzeFile) {
9552             nextGameMode = gameMode;
9553         } else {
9554             nextGameMode = EndOfGame;
9555         }
9556         pausing = FALSE;
9557         ModeHighlight();
9558     } else {
9559         nextGameMode = gameMode;
9560     }
9561
9562     if (appData.noChessProgram) {
9563         gameMode = nextGameMode;
9564         ModeHighlight();
9565         endingGame = 0; /* [HGM] crash */
9566         return;
9567     }
9568
9569     if (first.reuse) {
9570         /* Put first chess program into idle state */
9571         if (first.pr != NoProc &&
9572             (gameMode == MachinePlaysWhite ||
9573              gameMode == MachinePlaysBlack ||
9574              gameMode == TwoMachinesPlay ||
9575              gameMode == IcsPlayingWhite ||
9576              gameMode == IcsPlayingBlack ||
9577              gameMode == BeginningOfGame)) {
9578             SendToProgram("force\n", &first);
9579             if (first.usePing) {
9580               char buf[MSG_SIZ];
9581               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9582               SendToProgram(buf, &first);
9583             }
9584         }
9585     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9586         /* Kill off first chess program */
9587         if (first.isr != NULL)
9588           RemoveInputSource(first.isr);
9589         first.isr = NULL;
9590
9591         if (first.pr != NoProc) {
9592             ExitAnalyzeMode();
9593             DoSleep( appData.delayBeforeQuit );
9594             SendToProgram("quit\n", &first);
9595             DoSleep( appData.delayAfterQuit );
9596             DestroyChildProcess(first.pr, first.useSigterm);
9597         }
9598         first.pr = NoProc;
9599     }
9600     if (second.reuse) {
9601         /* Put second chess program into idle state */
9602         if (second.pr != NoProc &&
9603             gameMode == TwoMachinesPlay) {
9604             SendToProgram("force\n", &second);
9605             if (second.usePing) {
9606               char buf[MSG_SIZ];
9607               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9608               SendToProgram(buf, &second);
9609             }
9610         }
9611     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9612         /* Kill off second chess program */
9613         if (second.isr != NULL)
9614           RemoveInputSource(second.isr);
9615         second.isr = NULL;
9616
9617         if (second.pr != NoProc) {
9618             DoSleep( appData.delayBeforeQuit );
9619             SendToProgram("quit\n", &second);
9620             DoSleep( appData.delayAfterQuit );
9621             DestroyChildProcess(second.pr, second.useSigterm);
9622         }
9623         second.pr = NoProc;
9624     }
9625
9626     if (matchMode && gameMode == TwoMachinesPlay) {
9627         switch (result) {
9628         case WhiteWins:
9629           if (first.twoMachinesColor[0] == 'w') {
9630             first.matchWins++;
9631           } else {
9632             second.matchWins++;
9633           }
9634           break;
9635         case BlackWins:
9636           if (first.twoMachinesColor[0] == 'b') {
9637             first.matchWins++;
9638           } else {
9639             second.matchWins++;
9640           }
9641           break;
9642         default:
9643           break;
9644         }
9645         if (matchGame < appData.matchGames) {
9646             char *tmp;
9647             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9648                 tmp = first.twoMachinesColor;
9649                 first.twoMachinesColor = second.twoMachinesColor;
9650                 second.twoMachinesColor = tmp;
9651             }
9652             gameMode = nextGameMode;
9653             matchGame++;
9654             if(appData.matchPause>10000 || appData.matchPause<10)
9655                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9656             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9657             endingGame = 0; /* [HGM] crash */
9658             return;
9659         } else {
9660             gameMode = nextGameMode;
9661             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9662                      first.tidy, second.tidy,
9663                      first.matchWins, second.matchWins,
9664                      appData.matchGames - (first.matchWins + second.matchWins));
9665             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9666             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9667                 first.twoMachinesColor = "black\n";
9668                 second.twoMachinesColor = "white\n";
9669             } else {
9670                 first.twoMachinesColor = "white\n";
9671                 second.twoMachinesColor = "black\n";
9672             }
9673         }
9674     }
9675     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9676         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9677       ExitAnalyzeMode();
9678     gameMode = nextGameMode;
9679     ModeHighlight();
9680     endingGame = 0;  /* [HGM] crash */
9681     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9682       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9683         matchMode = FALSE; appData.matchGames = matchGame = 0;
9684         DisplayNote(buf);
9685       }
9686     }
9687 }
9688
9689 /* Assumes program was just initialized (initString sent).
9690    Leaves program in force mode. */
9691 void
9692 FeedMovesToProgram(cps, upto)
9693      ChessProgramState *cps;
9694      int upto;
9695 {
9696     int i;
9697
9698     if (appData.debugMode)
9699       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9700               startedFromSetupPosition ? "position and " : "",
9701               backwardMostMove, upto, cps->which);
9702     if(currentlyInitializedVariant != gameInfo.variant) {
9703       char buf[MSG_SIZ];
9704         // [HGM] variantswitch: make engine aware of new variant
9705         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9706                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9707         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9708         SendToProgram(buf, cps);
9709         currentlyInitializedVariant = gameInfo.variant;
9710     }
9711     SendToProgram("force\n", cps);
9712     if (startedFromSetupPosition) {
9713         SendBoard(cps, backwardMostMove);
9714     if (appData.debugMode) {
9715         fprintf(debugFP, "feedMoves\n");
9716     }
9717     }
9718     for (i = backwardMostMove; i < upto; i++) {
9719         SendMoveToProgram(i, cps);
9720     }
9721 }
9722
9723
9724 void
9725 ResurrectChessProgram()
9726 {
9727      /* The chess program may have exited.
9728         If so, restart it and feed it all the moves made so far. */
9729
9730     if (appData.noChessProgram || first.pr != NoProc) return;
9731
9732     StartChessProgram(&first);
9733     InitChessProgram(&first, FALSE);
9734     FeedMovesToProgram(&first, currentMove);
9735
9736     if (!first.sendTime) {
9737         /* can't tell gnuchess what its clock should read,
9738            so we bow to its notion. */
9739         ResetClocks();
9740         timeRemaining[0][currentMove] = whiteTimeRemaining;
9741         timeRemaining[1][currentMove] = blackTimeRemaining;
9742     }
9743
9744     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9745                 appData.icsEngineAnalyze) && first.analysisSupport) {
9746       SendToProgram("analyze\n", &first);
9747       first.analyzing = TRUE;
9748     }
9749 }
9750
9751 /*
9752  * Button procedures
9753  */
9754 void
9755 Reset(redraw, init)
9756      int redraw, init;
9757 {
9758     int i;
9759
9760     if (appData.debugMode) {
9761         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9762                 redraw, init, gameMode);
9763     }
9764     CleanupTail(); // [HGM] vari: delete any stored variations
9765     pausing = pauseExamInvalid = FALSE;
9766     startedFromSetupPosition = blackPlaysFirst = FALSE;
9767     firstMove = TRUE;
9768     whiteFlag = blackFlag = FALSE;
9769     userOfferedDraw = FALSE;
9770     hintRequested = bookRequested = FALSE;
9771     first.maybeThinking = FALSE;
9772     second.maybeThinking = FALSE;
9773     first.bookSuspend = FALSE; // [HGM] book
9774     second.bookSuspend = FALSE;
9775     thinkOutput[0] = NULLCHAR;
9776     lastHint[0] = NULLCHAR;
9777     ClearGameInfo(&gameInfo);
9778     gameInfo.variant = StringToVariant(appData.variant);
9779     ics_user_moved = ics_clock_paused = FALSE;
9780     ics_getting_history = H_FALSE;
9781     ics_gamenum = -1;
9782     white_holding[0] = black_holding[0] = NULLCHAR;
9783     ClearProgramStats();
9784     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9785
9786     ResetFrontEnd();
9787     ClearHighlights();
9788     flipView = appData.flipView;
9789     ClearPremoveHighlights();
9790     gotPremove = FALSE;
9791     alarmSounded = FALSE;
9792
9793     GameEnds(EndOfFile, NULL, GE_PLAYER);
9794     if(appData.serverMovesName != NULL) {
9795         /* [HGM] prepare to make moves file for broadcasting */
9796         clock_t t = clock();
9797         if(serverMoves != NULL) fclose(serverMoves);
9798         serverMoves = fopen(appData.serverMovesName, "r");
9799         if(serverMoves != NULL) {
9800             fclose(serverMoves);
9801             /* delay 15 sec before overwriting, so all clients can see end */
9802             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9803         }
9804         serverMoves = fopen(appData.serverMovesName, "w");
9805     }
9806
9807     ExitAnalyzeMode();
9808     gameMode = BeginningOfGame;
9809     ModeHighlight();
9810     if(appData.icsActive) gameInfo.variant = VariantNormal;
9811     currentMove = forwardMostMove = backwardMostMove = 0;
9812     InitPosition(redraw);
9813     for (i = 0; i < MAX_MOVES; i++) {
9814         if (commentList[i] != NULL) {
9815             free(commentList[i]);
9816             commentList[i] = NULL;
9817         }
9818     }
9819     ResetClocks();
9820     timeRemaining[0][0] = whiteTimeRemaining;
9821     timeRemaining[1][0] = blackTimeRemaining;
9822     if (first.pr == NULL) {
9823         StartChessProgram(&first);
9824     }
9825     if (init) {
9826             InitChessProgram(&first, startedFromSetupPosition);
9827     }
9828     DisplayTitle("");
9829     DisplayMessage("", "");
9830     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9831     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9832 }
9833
9834 void
9835 AutoPlayGameLoop()
9836 {
9837     for (;;) {
9838         if (!AutoPlayOneMove())
9839           return;
9840         if (matchMode || appData.timeDelay == 0)
9841           continue;
9842         if (appData.timeDelay < 0)
9843           return;
9844         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9845         break;
9846     }
9847 }
9848
9849
9850 int
9851 AutoPlayOneMove()
9852 {
9853     int fromX, fromY, toX, toY;
9854
9855     if (appData.debugMode) {
9856       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9857     }
9858
9859     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9860       return FALSE;
9861
9862     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9863       pvInfoList[currentMove].depth = programStats.depth;
9864       pvInfoList[currentMove].score = programStats.score;
9865       pvInfoList[currentMove].time  = 0;
9866       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9867     }
9868
9869     if (currentMove >= forwardMostMove) {
9870       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9871       gameMode = EditGame;
9872       ModeHighlight();
9873
9874       /* [AS] Clear current move marker at the end of a game */
9875       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9876
9877       return FALSE;
9878     }
9879
9880     toX = moveList[currentMove][2] - AAA;
9881     toY = moveList[currentMove][3] - ONE;
9882
9883     if (moveList[currentMove][1] == '@') {
9884         if (appData.highlightLastMove) {
9885             SetHighlights(-1, -1, toX, toY);
9886         }
9887     } else {
9888         fromX = moveList[currentMove][0] - AAA;
9889         fromY = moveList[currentMove][1] - ONE;
9890
9891         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9892
9893         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9894
9895         if (appData.highlightLastMove) {
9896             SetHighlights(fromX, fromY, toX, toY);
9897         }
9898     }
9899     DisplayMove(currentMove);
9900     SendMoveToProgram(currentMove++, &first);
9901     DisplayBothClocks();
9902     DrawPosition(FALSE, boards[currentMove]);
9903     // [HGM] PV info: always display, routine tests if empty
9904     DisplayComment(currentMove - 1, commentList[currentMove]);
9905     return TRUE;
9906 }
9907
9908
9909 int
9910 LoadGameOneMove(readAhead)
9911      ChessMove readAhead;
9912 {
9913     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9914     char promoChar = NULLCHAR;
9915     ChessMove moveType;
9916     char move[MSG_SIZ];
9917     char *p, *q;
9918
9919     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9920         gameMode != AnalyzeMode && gameMode != Training) {
9921         gameFileFP = NULL;
9922         return FALSE;
9923     }
9924
9925     yyboardindex = forwardMostMove;
9926     if (readAhead != EndOfFile) {
9927       moveType = readAhead;
9928     } else {
9929       if (gameFileFP == NULL)
9930           return FALSE;
9931       moveType = (ChessMove) Myylex();
9932     }
9933
9934     done = FALSE;
9935     switch (moveType) {
9936       case Comment:
9937         if (appData.debugMode)
9938           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9939         p = yy_text;
9940
9941         /* append the comment but don't display it */
9942         AppendComment(currentMove, p, FALSE);
9943         return TRUE;
9944
9945       case WhiteCapturesEnPassant:
9946       case BlackCapturesEnPassant:
9947       case WhitePromotion:
9948       case BlackPromotion:
9949       case WhiteNonPromotion:
9950       case BlackNonPromotion:
9951       case NormalMove:
9952       case WhiteKingSideCastle:
9953       case WhiteQueenSideCastle:
9954       case BlackKingSideCastle:
9955       case BlackQueenSideCastle:
9956       case WhiteKingSideCastleWild:
9957       case WhiteQueenSideCastleWild:
9958       case BlackKingSideCastleWild:
9959       case BlackQueenSideCastleWild:
9960       /* PUSH Fabien */
9961       case WhiteHSideCastleFR:
9962       case WhiteASideCastleFR:
9963       case BlackHSideCastleFR:
9964       case BlackASideCastleFR:
9965       /* POP Fabien */
9966         if (appData.debugMode)
9967           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9968         fromX = currentMoveString[0] - AAA;
9969         fromY = currentMoveString[1] - ONE;
9970         toX = currentMoveString[2] - AAA;
9971         toY = currentMoveString[3] - ONE;
9972         promoChar = currentMoveString[4];
9973         break;
9974
9975       case WhiteDrop:
9976       case BlackDrop:
9977         if (appData.debugMode)
9978           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9979         fromX = moveType == WhiteDrop ?
9980           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9981         (int) CharToPiece(ToLower(currentMoveString[0]));
9982         fromY = DROP_RANK;
9983         toX = currentMoveString[2] - AAA;
9984         toY = currentMoveString[3] - ONE;
9985         break;
9986
9987       case WhiteWins:
9988       case BlackWins:
9989       case GameIsDrawn:
9990       case GameUnfinished:
9991         if (appData.debugMode)
9992           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9993         p = strchr(yy_text, '{');
9994         if (p == NULL) p = strchr(yy_text, '(');
9995         if (p == NULL) {
9996             p = yy_text;
9997             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9998         } else {
9999             q = strchr(p, *p == '{' ? '}' : ')');
10000             if (q != NULL) *q = NULLCHAR;
10001             p++;
10002         }
10003         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10004         GameEnds(moveType, p, GE_FILE);
10005         done = TRUE;
10006         if (cmailMsgLoaded) {
10007             ClearHighlights();
10008             flipView = WhiteOnMove(currentMove);
10009             if (moveType == GameUnfinished) flipView = !flipView;
10010             if (appData.debugMode)
10011               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10012         }
10013         break;
10014
10015       case EndOfFile:
10016         if (appData.debugMode)
10017           fprintf(debugFP, "Parser hit end of file\n");
10018         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10019           case MT_NONE:
10020           case MT_CHECK:
10021             break;
10022           case MT_CHECKMATE:
10023           case MT_STAINMATE:
10024             if (WhiteOnMove(currentMove)) {
10025                 GameEnds(BlackWins, "Black mates", GE_FILE);
10026             } else {
10027                 GameEnds(WhiteWins, "White mates", GE_FILE);
10028             }
10029             break;
10030           case MT_STALEMATE:
10031             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10032             break;
10033         }
10034         done = TRUE;
10035         break;
10036
10037       case MoveNumberOne:
10038         if (lastLoadGameStart == GNUChessGame) {
10039             /* GNUChessGames have numbers, but they aren't move numbers */
10040             if (appData.debugMode)
10041               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10042                       yy_text, (int) moveType);
10043             return LoadGameOneMove(EndOfFile); /* tail recursion */
10044         }
10045         /* else fall thru */
10046
10047       case XBoardGame:
10048       case GNUChessGame:
10049       case PGNTag:
10050         /* Reached start of next game in file */
10051         if (appData.debugMode)
10052           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10053         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10054           case MT_NONE:
10055           case MT_CHECK:
10056             break;
10057           case MT_CHECKMATE:
10058           case MT_STAINMATE:
10059             if (WhiteOnMove(currentMove)) {
10060                 GameEnds(BlackWins, "Black mates", GE_FILE);
10061             } else {
10062                 GameEnds(WhiteWins, "White mates", GE_FILE);
10063             }
10064             break;
10065           case MT_STALEMATE:
10066             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10067             break;
10068         }
10069         done = TRUE;
10070         break;
10071
10072       case PositionDiagram:     /* should not happen; ignore */
10073       case ElapsedTime:         /* ignore */
10074       case NAG:                 /* ignore */
10075         if (appData.debugMode)
10076           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10077                   yy_text, (int) moveType);
10078         return LoadGameOneMove(EndOfFile); /* tail recursion */
10079
10080       case IllegalMove:
10081         if (appData.testLegality) {
10082             if (appData.debugMode)
10083               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10084             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10085                     (forwardMostMove / 2) + 1,
10086                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10087             DisplayError(move, 0);
10088             done = TRUE;
10089         } else {
10090             if (appData.debugMode)
10091               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10092                       yy_text, currentMoveString);
10093             fromX = currentMoveString[0] - AAA;
10094             fromY = currentMoveString[1] - ONE;
10095             toX = currentMoveString[2] - AAA;
10096             toY = currentMoveString[3] - ONE;
10097             promoChar = currentMoveString[4];
10098         }
10099         break;
10100
10101       case AmbiguousMove:
10102         if (appData.debugMode)
10103           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10104         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10105                 (forwardMostMove / 2) + 1,
10106                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10107         DisplayError(move, 0);
10108         done = TRUE;
10109         break;
10110
10111       default:
10112       case ImpossibleMove:
10113         if (appData.debugMode)
10114           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10115         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10116                 (forwardMostMove / 2) + 1,
10117                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10118         DisplayError(move, 0);
10119         done = TRUE;
10120         break;
10121     }
10122
10123     if (done) {
10124         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10125             DrawPosition(FALSE, boards[currentMove]);
10126             DisplayBothClocks();
10127             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10128               DisplayComment(currentMove - 1, commentList[currentMove]);
10129         }
10130         (void) StopLoadGameTimer();
10131         gameFileFP = NULL;
10132         cmailOldMove = forwardMostMove;
10133         return FALSE;
10134     } else {
10135         /* currentMoveString is set as a side-effect of yylex */
10136
10137         thinkOutput[0] = NULLCHAR;
10138         MakeMove(fromX, fromY, toX, toY, promoChar);
10139         currentMove = forwardMostMove;
10140         return TRUE;
10141     }
10142 }
10143
10144 /* Load the nth game from the given file */
10145 int
10146 LoadGameFromFile(filename, n, title, useList)
10147      char *filename;
10148      int n;
10149      char *title;
10150      /*Boolean*/ int useList;
10151 {
10152     FILE *f;
10153     char buf[MSG_SIZ];
10154
10155     if (strcmp(filename, "-") == 0) {
10156         f = stdin;
10157         title = "stdin";
10158     } else {
10159         f = fopen(filename, "rb");
10160         if (f == NULL) {
10161           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10162             DisplayError(buf, errno);
10163             return FALSE;
10164         }
10165     }
10166     if (fseek(f, 0, 0) == -1) {
10167         /* f is not seekable; probably a pipe */
10168         useList = FALSE;
10169     }
10170     if (useList && n == 0) {
10171         int error = GameListBuild(f);
10172         if (error) {
10173             DisplayError(_("Cannot build game list"), error);
10174         } else if (!ListEmpty(&gameList) &&
10175                    ((ListGame *) gameList.tailPred)->number > 1) {
10176             GameListPopUp(f, title);
10177             return TRUE;
10178         }
10179         GameListDestroy();
10180         n = 1;
10181     }
10182     if (n == 0) n = 1;
10183     return LoadGame(f, n, title, FALSE);
10184 }
10185
10186
10187 void
10188 MakeRegisteredMove()
10189 {
10190     int fromX, fromY, toX, toY;
10191     char promoChar;
10192     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10193         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10194           case CMAIL_MOVE:
10195           case CMAIL_DRAW:
10196             if (appData.debugMode)
10197               fprintf(debugFP, "Restoring %s for game %d\n",
10198                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10199
10200             thinkOutput[0] = NULLCHAR;
10201             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10202             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10203             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10204             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10205             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10206             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10207             MakeMove(fromX, fromY, toX, toY, promoChar);
10208             ShowMove(fromX, fromY, toX, toY);
10209
10210             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10211               case MT_NONE:
10212               case MT_CHECK:
10213                 break;
10214
10215               case MT_CHECKMATE:
10216               case MT_STAINMATE:
10217                 if (WhiteOnMove(currentMove)) {
10218                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10219                 } else {
10220                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10221                 }
10222                 break;
10223
10224               case MT_STALEMATE:
10225                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10226                 break;
10227             }
10228
10229             break;
10230
10231           case CMAIL_RESIGN:
10232             if (WhiteOnMove(currentMove)) {
10233                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10234             } else {
10235                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10236             }
10237             break;
10238
10239           case CMAIL_ACCEPT:
10240             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10241             break;
10242
10243           default:
10244             break;
10245         }
10246     }
10247
10248     return;
10249 }
10250
10251 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10252 int
10253 CmailLoadGame(f, gameNumber, title, useList)
10254      FILE *f;
10255      int gameNumber;
10256      char *title;
10257      int useList;
10258 {
10259     int retVal;
10260
10261     if (gameNumber > nCmailGames) {
10262         DisplayError(_("No more games in this message"), 0);
10263         return FALSE;
10264     }
10265     if (f == lastLoadGameFP) {
10266         int offset = gameNumber - lastLoadGameNumber;
10267         if (offset == 0) {
10268             cmailMsg[0] = NULLCHAR;
10269             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10270                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10271                 nCmailMovesRegistered--;
10272             }
10273             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10274             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10275                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10276             }
10277         } else {
10278             if (! RegisterMove()) return FALSE;
10279         }
10280     }
10281
10282     retVal = LoadGame(f, gameNumber, title, useList);
10283
10284     /* Make move registered during previous look at this game, if any */
10285     MakeRegisteredMove();
10286
10287     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10288         commentList[currentMove]
10289           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10290         DisplayComment(currentMove - 1, commentList[currentMove]);
10291     }
10292
10293     return retVal;
10294 }
10295
10296 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10297 int
10298 ReloadGame(offset)
10299      int offset;
10300 {
10301     int gameNumber = lastLoadGameNumber + offset;
10302     if (lastLoadGameFP == NULL) {
10303         DisplayError(_("No game has been loaded yet"), 0);
10304         return FALSE;
10305     }
10306     if (gameNumber <= 0) {
10307         DisplayError(_("Can't back up any further"), 0);
10308         return FALSE;
10309     }
10310     if (cmailMsgLoaded) {
10311         return CmailLoadGame(lastLoadGameFP, gameNumber,
10312                              lastLoadGameTitle, lastLoadGameUseList);
10313     } else {
10314         return LoadGame(lastLoadGameFP, gameNumber,
10315                         lastLoadGameTitle, lastLoadGameUseList);
10316     }
10317 }
10318
10319
10320
10321 /* Load the nth game from open file f */
10322 int
10323 LoadGame(f, gameNumber, title, useList)
10324      FILE *f;
10325      int gameNumber;
10326      char *title;
10327      int useList;
10328 {
10329     ChessMove cm;
10330     char buf[MSG_SIZ];
10331     int gn = gameNumber;
10332     ListGame *lg = NULL;
10333     int numPGNTags = 0;
10334     int err;
10335     GameMode oldGameMode;
10336     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10337
10338     if (appData.debugMode)
10339         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10340
10341     if (gameMode == Training )
10342         SetTrainingModeOff();
10343
10344     oldGameMode = gameMode;
10345     if (gameMode != BeginningOfGame) {
10346       Reset(FALSE, TRUE);
10347     }
10348
10349     gameFileFP = f;
10350     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10351         fclose(lastLoadGameFP);
10352     }
10353
10354     if (useList) {
10355         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10356
10357         if (lg) {
10358             fseek(f, lg->offset, 0);
10359             GameListHighlight(gameNumber);
10360             gn = 1;
10361         }
10362         else {
10363             DisplayError(_("Game number out of range"), 0);
10364             return FALSE;
10365         }
10366     } else {
10367         GameListDestroy();
10368         if (fseek(f, 0, 0) == -1) {
10369             if (f == lastLoadGameFP ?
10370                 gameNumber == lastLoadGameNumber + 1 :
10371                 gameNumber == 1) {
10372                 gn = 1;
10373             } else {
10374                 DisplayError(_("Can't seek on game file"), 0);
10375                 return FALSE;
10376             }
10377         }
10378     }
10379     lastLoadGameFP = f;
10380     lastLoadGameNumber = gameNumber;
10381     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10382     lastLoadGameUseList = useList;
10383
10384     yynewfile(f);
10385
10386     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10387       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10388                 lg->gameInfo.black);
10389             DisplayTitle(buf);
10390     } else if (*title != NULLCHAR) {
10391         if (gameNumber > 1) {
10392           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10393             DisplayTitle(buf);
10394         } else {
10395             DisplayTitle(title);
10396         }
10397     }
10398
10399     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10400         gameMode = PlayFromGameFile;
10401         ModeHighlight();
10402     }
10403
10404     currentMove = forwardMostMove = backwardMostMove = 0;
10405     CopyBoard(boards[0], initialPosition);
10406     StopClocks();
10407
10408     /*
10409      * Skip the first gn-1 games in the file.
10410      * Also skip over anything that precedes an identifiable
10411      * start of game marker, to avoid being confused by
10412      * garbage at the start of the file.  Currently
10413      * recognized start of game markers are the move number "1",
10414      * the pattern "gnuchess .* game", the pattern
10415      * "^[#;%] [^ ]* game file", and a PGN tag block.
10416      * A game that starts with one of the latter two patterns
10417      * will also have a move number 1, possibly
10418      * following a position diagram.
10419      * 5-4-02: Let's try being more lenient and allowing a game to
10420      * start with an unnumbered move.  Does that break anything?
10421      */
10422     cm = lastLoadGameStart = EndOfFile;
10423     while (gn > 0) {
10424         yyboardindex = forwardMostMove;
10425         cm = (ChessMove) Myylex();
10426         switch (cm) {
10427           case EndOfFile:
10428             if (cmailMsgLoaded) {
10429                 nCmailGames = CMAIL_MAX_GAMES - gn;
10430             } else {
10431                 Reset(TRUE, TRUE);
10432                 DisplayError(_("Game not found in file"), 0);
10433             }
10434             return FALSE;
10435
10436           case GNUChessGame:
10437           case XBoardGame:
10438             gn--;
10439             lastLoadGameStart = cm;
10440             break;
10441
10442           case MoveNumberOne:
10443             switch (lastLoadGameStart) {
10444               case GNUChessGame:
10445               case XBoardGame:
10446               case PGNTag:
10447                 break;
10448               case MoveNumberOne:
10449               case EndOfFile:
10450                 gn--;           /* count this game */
10451                 lastLoadGameStart = cm;
10452                 break;
10453               default:
10454                 /* impossible */
10455                 break;
10456             }
10457             break;
10458
10459           case PGNTag:
10460             switch (lastLoadGameStart) {
10461               case GNUChessGame:
10462               case PGNTag:
10463               case MoveNumberOne:
10464               case EndOfFile:
10465                 gn--;           /* count this game */
10466                 lastLoadGameStart = cm;
10467                 break;
10468               case XBoardGame:
10469                 lastLoadGameStart = cm; /* game counted already */
10470                 break;
10471               default:
10472                 /* impossible */
10473                 break;
10474             }
10475             if (gn > 0) {
10476                 do {
10477                     yyboardindex = forwardMostMove;
10478                     cm = (ChessMove) Myylex();
10479                 } while (cm == PGNTag || cm == Comment);
10480             }
10481             break;
10482
10483           case WhiteWins:
10484           case BlackWins:
10485           case GameIsDrawn:
10486             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10487                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10488                     != CMAIL_OLD_RESULT) {
10489                     nCmailResults ++ ;
10490                     cmailResult[  CMAIL_MAX_GAMES
10491                                 - gn - 1] = CMAIL_OLD_RESULT;
10492                 }
10493             }
10494             break;
10495
10496           case NormalMove:
10497             /* Only a NormalMove can be at the start of a game
10498              * without a position diagram. */
10499             if (lastLoadGameStart == EndOfFile ) {
10500               gn--;
10501               lastLoadGameStart = MoveNumberOne;
10502             }
10503             break;
10504
10505           default:
10506             break;
10507         }
10508     }
10509
10510     if (appData.debugMode)
10511       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10512
10513     if (cm == XBoardGame) {
10514         /* Skip any header junk before position diagram and/or move 1 */
10515         for (;;) {
10516             yyboardindex = forwardMostMove;
10517             cm = (ChessMove) Myylex();
10518
10519             if (cm == EndOfFile ||
10520                 cm == GNUChessGame || cm == XBoardGame) {
10521                 /* Empty game; pretend end-of-file and handle later */
10522                 cm = EndOfFile;
10523                 break;
10524             }
10525
10526             if (cm == MoveNumberOne || cm == PositionDiagram ||
10527                 cm == PGNTag || cm == Comment)
10528               break;
10529         }
10530     } else if (cm == GNUChessGame) {
10531         if (gameInfo.event != NULL) {
10532             free(gameInfo.event);
10533         }
10534         gameInfo.event = StrSave(yy_text);
10535     }
10536
10537     startedFromSetupPosition = FALSE;
10538     while (cm == PGNTag) {
10539         if (appData.debugMode)
10540           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10541         err = ParsePGNTag(yy_text, &gameInfo);
10542         if (!err) numPGNTags++;
10543
10544         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10545         if(gameInfo.variant != oldVariant) {
10546             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10547             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10548             InitPosition(TRUE);
10549             oldVariant = gameInfo.variant;
10550             if (appData.debugMode)
10551               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10552         }
10553
10554
10555         if (gameInfo.fen != NULL) {
10556           Board initial_position;
10557           startedFromSetupPosition = TRUE;
10558           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10559             Reset(TRUE, TRUE);
10560             DisplayError(_("Bad FEN position in file"), 0);
10561             return FALSE;
10562           }
10563           CopyBoard(boards[0], initial_position);
10564           if (blackPlaysFirst) {
10565             currentMove = forwardMostMove = backwardMostMove = 1;
10566             CopyBoard(boards[1], initial_position);
10567             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10568             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10569             timeRemaining[0][1] = whiteTimeRemaining;
10570             timeRemaining[1][1] = blackTimeRemaining;
10571             if (commentList[0] != NULL) {
10572               commentList[1] = commentList[0];
10573               commentList[0] = NULL;
10574             }
10575           } else {
10576             currentMove = forwardMostMove = backwardMostMove = 0;
10577           }
10578           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10579           {   int i;
10580               initialRulePlies = FENrulePlies;
10581               for( i=0; i< nrCastlingRights; i++ )
10582                   initialRights[i] = initial_position[CASTLING][i];
10583           }
10584           yyboardindex = forwardMostMove;
10585           free(gameInfo.fen);
10586           gameInfo.fen = NULL;
10587         }
10588
10589         yyboardindex = forwardMostMove;
10590         cm = (ChessMove) Myylex();
10591
10592         /* Handle comments interspersed among the tags */
10593         while (cm == Comment) {
10594             char *p;
10595             if (appData.debugMode)
10596               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10597             p = yy_text;
10598             AppendComment(currentMove, p, FALSE);
10599             yyboardindex = forwardMostMove;
10600             cm = (ChessMove) Myylex();
10601         }
10602     }
10603
10604     /* don't rely on existence of Event tag since if game was
10605      * pasted from clipboard the Event tag may not exist
10606      */
10607     if (numPGNTags > 0){
10608         char *tags;
10609         if (gameInfo.variant == VariantNormal) {
10610           VariantClass v = StringToVariant(gameInfo.event);
10611           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10612           if(v < VariantShogi) gameInfo.variant = v;
10613         }
10614         if (!matchMode) {
10615           if( appData.autoDisplayTags ) {
10616             tags = PGNTags(&gameInfo);
10617             TagsPopUp(tags, CmailMsg());
10618             free(tags);
10619           }
10620         }
10621     } else {
10622         /* Make something up, but don't display it now */
10623         SetGameInfo();
10624         TagsPopDown();
10625     }
10626
10627     if (cm == PositionDiagram) {
10628         int i, j;
10629         char *p;
10630         Board initial_position;
10631
10632         if (appData.debugMode)
10633           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10634
10635         if (!startedFromSetupPosition) {
10636             p = yy_text;
10637             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10638               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10639                 switch (*p) {
10640                   case '{':
10641                   case '[':
10642                   case '-':
10643                   case ' ':
10644                   case '\t':
10645                   case '\n':
10646                   case '\r':
10647                     break;
10648                   default:
10649                     initial_position[i][j++] = CharToPiece(*p);
10650                     break;
10651                 }
10652             while (*p == ' ' || *p == '\t' ||
10653                    *p == '\n' || *p == '\r') p++;
10654
10655             if (strncmp(p, "black", strlen("black"))==0)
10656               blackPlaysFirst = TRUE;
10657             else
10658               blackPlaysFirst = FALSE;
10659             startedFromSetupPosition = TRUE;
10660
10661             CopyBoard(boards[0], initial_position);
10662             if (blackPlaysFirst) {
10663                 currentMove = forwardMostMove = backwardMostMove = 1;
10664                 CopyBoard(boards[1], initial_position);
10665                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10666                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10667                 timeRemaining[0][1] = whiteTimeRemaining;
10668                 timeRemaining[1][1] = blackTimeRemaining;
10669                 if (commentList[0] != NULL) {
10670                     commentList[1] = commentList[0];
10671                     commentList[0] = NULL;
10672                 }
10673             } else {
10674                 currentMove = forwardMostMove = backwardMostMove = 0;
10675             }
10676         }
10677         yyboardindex = forwardMostMove;
10678         cm = (ChessMove) Myylex();
10679     }
10680
10681     if (first.pr == NoProc) {
10682         StartChessProgram(&first);
10683     }
10684     InitChessProgram(&first, FALSE);
10685     SendToProgram("force\n", &first);
10686     if (startedFromSetupPosition) {
10687         SendBoard(&first, forwardMostMove);
10688     if (appData.debugMode) {
10689         fprintf(debugFP, "Load Game\n");
10690     }
10691         DisplayBothClocks();
10692     }
10693
10694     /* [HGM] server: flag to write setup moves in broadcast file as one */
10695     loadFlag = appData.suppressLoadMoves;
10696
10697     while (cm == Comment) {
10698         char *p;
10699         if (appData.debugMode)
10700           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10701         p = yy_text;
10702         AppendComment(currentMove, p, FALSE);
10703         yyboardindex = forwardMostMove;
10704         cm = (ChessMove) Myylex();
10705     }
10706
10707     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10708         cm == WhiteWins || cm == BlackWins ||
10709         cm == GameIsDrawn || cm == GameUnfinished) {
10710         DisplayMessage("", _("No moves in game"));
10711         if (cmailMsgLoaded) {
10712             if (appData.debugMode)
10713               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10714             ClearHighlights();
10715             flipView = FALSE;
10716         }
10717         DrawPosition(FALSE, boards[currentMove]);
10718         DisplayBothClocks();
10719         gameMode = EditGame;
10720         ModeHighlight();
10721         gameFileFP = NULL;
10722         cmailOldMove = 0;
10723         return TRUE;
10724     }
10725
10726     // [HGM] PV info: routine tests if comment empty
10727     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10728         DisplayComment(currentMove - 1, commentList[currentMove]);
10729     }
10730     if (!matchMode && appData.timeDelay != 0)
10731       DrawPosition(FALSE, boards[currentMove]);
10732
10733     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10734       programStats.ok_to_send = 1;
10735     }
10736
10737     /* if the first token after the PGN tags is a move
10738      * and not move number 1, retrieve it from the parser
10739      */
10740     if (cm != MoveNumberOne)
10741         LoadGameOneMove(cm);
10742
10743     /* load the remaining moves from the file */
10744     while (LoadGameOneMove(EndOfFile)) {
10745       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10746       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10747     }
10748
10749     /* rewind to the start of the game */
10750     currentMove = backwardMostMove;
10751
10752     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10753
10754     if (oldGameMode == AnalyzeFile ||
10755         oldGameMode == AnalyzeMode) {
10756       AnalyzeFileEvent();
10757     }
10758
10759     if (matchMode || appData.timeDelay == 0) {
10760       ToEndEvent();
10761       gameMode = EditGame;
10762       ModeHighlight();
10763     } else if (appData.timeDelay > 0) {
10764       AutoPlayGameLoop();
10765     }
10766
10767     if (appData.debugMode)
10768         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10769
10770     loadFlag = 0; /* [HGM] true game starts */
10771     return TRUE;
10772 }
10773
10774 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10775 int
10776 ReloadPosition(offset)
10777      int offset;
10778 {
10779     int positionNumber = lastLoadPositionNumber + offset;
10780     if (lastLoadPositionFP == NULL) {
10781         DisplayError(_("No position has been loaded yet"), 0);
10782         return FALSE;
10783     }
10784     if (positionNumber <= 0) {
10785         DisplayError(_("Can't back up any further"), 0);
10786         return FALSE;
10787     }
10788     return LoadPosition(lastLoadPositionFP, positionNumber,
10789                         lastLoadPositionTitle);
10790 }
10791
10792 /* Load the nth position from the given file */
10793 int
10794 LoadPositionFromFile(filename, n, title)
10795      char *filename;
10796      int n;
10797      char *title;
10798 {
10799     FILE *f;
10800     char buf[MSG_SIZ];
10801
10802     if (strcmp(filename, "-") == 0) {
10803         return LoadPosition(stdin, n, "stdin");
10804     } else {
10805         f = fopen(filename, "rb");
10806         if (f == NULL) {
10807             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10808             DisplayError(buf, errno);
10809             return FALSE;
10810         } else {
10811             return LoadPosition(f, n, title);
10812         }
10813     }
10814 }
10815
10816 /* Load the nth position from the given open file, and close it */
10817 int
10818 LoadPosition(f, positionNumber, title)
10819      FILE *f;
10820      int positionNumber;
10821      char *title;
10822 {
10823     char *p, line[MSG_SIZ];
10824     Board initial_position;
10825     int i, j, fenMode, pn;
10826
10827     if (gameMode == Training )
10828         SetTrainingModeOff();
10829
10830     if (gameMode != BeginningOfGame) {
10831         Reset(FALSE, TRUE);
10832     }
10833     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10834         fclose(lastLoadPositionFP);
10835     }
10836     if (positionNumber == 0) positionNumber = 1;
10837     lastLoadPositionFP = f;
10838     lastLoadPositionNumber = positionNumber;
10839     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10840     if (first.pr == NoProc) {
10841       StartChessProgram(&first);
10842       InitChessProgram(&first, FALSE);
10843     }
10844     pn = positionNumber;
10845     if (positionNumber < 0) {
10846         /* Negative position number means to seek to that byte offset */
10847         if (fseek(f, -positionNumber, 0) == -1) {
10848             DisplayError(_("Can't seek on position file"), 0);
10849             return FALSE;
10850         };
10851         pn = 1;
10852     } else {
10853         if (fseek(f, 0, 0) == -1) {
10854             if (f == lastLoadPositionFP ?
10855                 positionNumber == lastLoadPositionNumber + 1 :
10856                 positionNumber == 1) {
10857                 pn = 1;
10858             } else {
10859                 DisplayError(_("Can't seek on position file"), 0);
10860                 return FALSE;
10861             }
10862         }
10863     }
10864     /* See if this file is FEN or old-style xboard */
10865     if (fgets(line, MSG_SIZ, f) == NULL) {
10866         DisplayError(_("Position not found in file"), 0);
10867         return FALSE;
10868     }
10869     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10870     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10871
10872     if (pn >= 2) {
10873         if (fenMode || line[0] == '#') pn--;
10874         while (pn > 0) {
10875             /* skip positions before number pn */
10876             if (fgets(line, MSG_SIZ, f) == NULL) {
10877                 Reset(TRUE, TRUE);
10878                 DisplayError(_("Position not found in file"), 0);
10879                 return FALSE;
10880             }
10881             if (fenMode || line[0] == '#') pn--;
10882         }
10883     }
10884
10885     if (fenMode) {
10886         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10887             DisplayError(_("Bad FEN position in file"), 0);
10888             return FALSE;
10889         }
10890     } else {
10891         (void) fgets(line, MSG_SIZ, f);
10892         (void) fgets(line, MSG_SIZ, f);
10893
10894         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10895             (void) fgets(line, MSG_SIZ, f);
10896             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10897                 if (*p == ' ')
10898                   continue;
10899                 initial_position[i][j++] = CharToPiece(*p);
10900             }
10901         }
10902
10903         blackPlaysFirst = FALSE;
10904         if (!feof(f)) {
10905             (void) fgets(line, MSG_SIZ, f);
10906             if (strncmp(line, "black", strlen("black"))==0)
10907               blackPlaysFirst = TRUE;
10908         }
10909     }
10910     startedFromSetupPosition = TRUE;
10911
10912     SendToProgram("force\n", &first);
10913     CopyBoard(boards[0], initial_position);
10914     if (blackPlaysFirst) {
10915         currentMove = forwardMostMove = backwardMostMove = 1;
10916         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10917         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10918         CopyBoard(boards[1], initial_position);
10919         DisplayMessage("", _("Black to play"));
10920     } else {
10921         currentMove = forwardMostMove = backwardMostMove = 0;
10922         DisplayMessage("", _("White to play"));
10923     }
10924     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10925     SendBoard(&first, forwardMostMove);
10926     if (appData.debugMode) {
10927 int i, j;
10928   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10929   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10930         fprintf(debugFP, "Load Position\n");
10931     }
10932
10933     if (positionNumber > 1) {
10934       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10935         DisplayTitle(line);
10936     } else {
10937         DisplayTitle(title);
10938     }
10939     gameMode = EditGame;
10940     ModeHighlight();
10941     ResetClocks();
10942     timeRemaining[0][1] = whiteTimeRemaining;
10943     timeRemaining[1][1] = blackTimeRemaining;
10944     DrawPosition(FALSE, boards[currentMove]);
10945
10946     return TRUE;
10947 }
10948
10949
10950 void
10951 CopyPlayerNameIntoFileName(dest, src)
10952      char **dest, *src;
10953 {
10954     while (*src != NULLCHAR && *src != ',') {
10955         if (*src == ' ') {
10956             *(*dest)++ = '_';
10957             src++;
10958         } else {
10959             *(*dest)++ = *src++;
10960         }
10961     }
10962 }
10963
10964 char *DefaultFileName(ext)
10965      char *ext;
10966 {
10967     static char def[MSG_SIZ];
10968     char *p;
10969
10970     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10971         p = def;
10972         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10973         *p++ = '-';
10974         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10975         *p++ = '.';
10976         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10977     } else {
10978         def[0] = NULLCHAR;
10979     }
10980     return def;
10981 }
10982
10983 /* Save the current game to the given file */
10984 int
10985 SaveGameToFile(filename, append)
10986      char *filename;
10987      int append;
10988 {
10989     FILE *f;
10990     char buf[MSG_SIZ];
10991
10992     if (strcmp(filename, "-") == 0) {
10993         return SaveGame(stdout, 0, NULL);
10994     } else {
10995         f = fopen(filename, append ? "a" : "w");
10996         if (f == NULL) {
10997             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10998             DisplayError(buf, errno);
10999             return FALSE;
11000         } else {
11001             return SaveGame(f, 0, NULL);
11002         }
11003     }
11004 }
11005
11006 char *
11007 SavePart(str)
11008      char *str;
11009 {
11010     static char buf[MSG_SIZ];
11011     char *p;
11012
11013     p = strchr(str, ' ');
11014     if (p == NULL) return str;
11015     strncpy(buf, str, p - str);
11016     buf[p - str] = NULLCHAR;
11017     return buf;
11018 }
11019
11020 #define PGN_MAX_LINE 75
11021
11022 #define PGN_SIDE_WHITE  0
11023 #define PGN_SIDE_BLACK  1
11024
11025 /* [AS] */
11026 static int FindFirstMoveOutOfBook( int side )
11027 {
11028     int result = -1;
11029
11030     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11031         int index = backwardMostMove;
11032         int has_book_hit = 0;
11033
11034         if( (index % 2) != side ) {
11035             index++;
11036         }
11037
11038         while( index < forwardMostMove ) {
11039             /* Check to see if engine is in book */
11040             int depth = pvInfoList[index].depth;
11041             int score = pvInfoList[index].score;
11042             int in_book = 0;
11043
11044             if( depth <= 2 ) {
11045                 in_book = 1;
11046             }
11047             else if( score == 0 && depth == 63 ) {
11048                 in_book = 1; /* Zappa */
11049             }
11050             else if( score == 2 && depth == 99 ) {
11051                 in_book = 1; /* Abrok */
11052             }
11053
11054             has_book_hit += in_book;
11055
11056             if( ! in_book ) {
11057                 result = index;
11058
11059                 break;
11060             }
11061
11062             index += 2;
11063         }
11064     }
11065
11066     return result;
11067 }
11068
11069 /* [AS] */
11070 void GetOutOfBookInfo( char * buf )
11071 {
11072     int oob[2];
11073     int i;
11074     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11075
11076     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11077     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11078
11079     *buf = '\0';
11080
11081     if( oob[0] >= 0 || oob[1] >= 0 ) {
11082         for( i=0; i<2; i++ ) {
11083             int idx = oob[i];
11084
11085             if( idx >= 0 ) {
11086                 if( i > 0 && oob[0] >= 0 ) {
11087                     strcat( buf, "   " );
11088                 }
11089
11090                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11091                 sprintf( buf+strlen(buf), "%s%.2f",
11092                     pvInfoList[idx].score >= 0 ? "+" : "",
11093                     pvInfoList[idx].score / 100.0 );
11094             }
11095         }
11096     }
11097 }
11098
11099 /* Save game in PGN style and close the file */
11100 int
11101 SaveGamePGN(f)
11102      FILE *f;
11103 {
11104     int i, offset, linelen, newblock;
11105     time_t tm;
11106 //    char *movetext;
11107     char numtext[32];
11108     int movelen, numlen, blank;
11109     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11110
11111     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11112
11113     tm = time((time_t *) NULL);
11114
11115     PrintPGNTags(f, &gameInfo);
11116
11117     if (backwardMostMove > 0 || startedFromSetupPosition) {
11118         char *fen = PositionToFEN(backwardMostMove, NULL);
11119         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11120         fprintf(f, "\n{--------------\n");
11121         PrintPosition(f, backwardMostMove);
11122         fprintf(f, "--------------}\n");
11123         free(fen);
11124     }
11125     else {
11126         /* [AS] Out of book annotation */
11127         if( appData.saveOutOfBookInfo ) {
11128             char buf[64];
11129
11130             GetOutOfBookInfo( buf );
11131
11132             if( buf[0] != '\0' ) {
11133                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11134             }
11135         }
11136
11137         fprintf(f, "\n");
11138     }
11139
11140     i = backwardMostMove;
11141     linelen = 0;
11142     newblock = TRUE;
11143
11144     while (i < forwardMostMove) {
11145         /* Print comments preceding this move */
11146         if (commentList[i] != NULL) {
11147             if (linelen > 0) fprintf(f, "\n");
11148             fprintf(f, "%s", commentList[i]);
11149             linelen = 0;
11150             newblock = TRUE;
11151         }
11152
11153         /* Format move number */
11154         if ((i % 2) == 0)
11155           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11156         else
11157           if (newblock)
11158             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11159           else
11160             numtext[0] = NULLCHAR;
11161
11162         numlen = strlen(numtext);
11163         newblock = FALSE;
11164
11165         /* Print move number */
11166         blank = linelen > 0 && numlen > 0;
11167         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11168             fprintf(f, "\n");
11169             linelen = 0;
11170             blank = 0;
11171         }
11172         if (blank) {
11173             fprintf(f, " ");
11174             linelen++;
11175         }
11176         fprintf(f, "%s", numtext);
11177         linelen += numlen;
11178
11179         /* Get move */
11180         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11181         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11182
11183         /* Print move */
11184         blank = linelen > 0 && movelen > 0;
11185         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11186             fprintf(f, "\n");
11187             linelen = 0;
11188             blank = 0;
11189         }
11190         if (blank) {
11191             fprintf(f, " ");
11192             linelen++;
11193         }
11194         fprintf(f, "%s", move_buffer);
11195         linelen += movelen;
11196
11197         /* [AS] Add PV info if present */
11198         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11199             /* [HGM] add time */
11200             char buf[MSG_SIZ]; int seconds;
11201
11202             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11203
11204             if( seconds <= 0)
11205               buf[0] = 0;
11206             else
11207               if( seconds < 30 )
11208                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11209               else
11210                 {
11211                   seconds = (seconds + 4)/10; // round to full seconds
11212                   if( seconds < 60 )
11213                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11214                   else
11215                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11216                 }
11217
11218             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11219                       pvInfoList[i].score >= 0 ? "+" : "",
11220                       pvInfoList[i].score / 100.0,
11221                       pvInfoList[i].depth,
11222                       buf );
11223
11224             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11225
11226             /* Print score/depth */
11227             blank = linelen > 0 && movelen > 0;
11228             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11229                 fprintf(f, "\n");
11230                 linelen = 0;
11231                 blank = 0;
11232             }
11233             if (blank) {
11234                 fprintf(f, " ");
11235                 linelen++;
11236             }
11237             fprintf(f, "%s", move_buffer);
11238             linelen += movelen;
11239         }
11240
11241         i++;
11242     }
11243
11244     /* Start a new line */
11245     if (linelen > 0) fprintf(f, "\n");
11246
11247     /* Print comments after last move */
11248     if (commentList[i] != NULL) {
11249         fprintf(f, "%s\n", commentList[i]);
11250     }
11251
11252     /* Print result */
11253     if (gameInfo.resultDetails != NULL &&
11254         gameInfo.resultDetails[0] != NULLCHAR) {
11255         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11256                 PGNResult(gameInfo.result));
11257     } else {
11258         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11259     }
11260
11261     fclose(f);
11262     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11263     return TRUE;
11264 }
11265
11266 /* Save game in old style and close the file */
11267 int
11268 SaveGameOldStyle(f)
11269      FILE *f;
11270 {
11271     int i, offset;
11272     time_t tm;
11273
11274     tm = time((time_t *) NULL);
11275
11276     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11277     PrintOpponents(f);
11278
11279     if (backwardMostMove > 0 || startedFromSetupPosition) {
11280         fprintf(f, "\n[--------------\n");
11281         PrintPosition(f, backwardMostMove);
11282         fprintf(f, "--------------]\n");
11283     } else {
11284         fprintf(f, "\n");
11285     }
11286
11287     i = backwardMostMove;
11288     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11289
11290     while (i < forwardMostMove) {
11291         if (commentList[i] != NULL) {
11292             fprintf(f, "[%s]\n", commentList[i]);
11293         }
11294
11295         if ((i % 2) == 1) {
11296             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11297             i++;
11298         } else {
11299             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11300             i++;
11301             if (commentList[i] != NULL) {
11302                 fprintf(f, "\n");
11303                 continue;
11304             }
11305             if (i >= forwardMostMove) {
11306                 fprintf(f, "\n");
11307                 break;
11308             }
11309             fprintf(f, "%s\n", parseList[i]);
11310             i++;
11311         }
11312     }
11313
11314     if (commentList[i] != NULL) {
11315         fprintf(f, "[%s]\n", commentList[i]);
11316     }
11317
11318     /* This isn't really the old style, but it's close enough */
11319     if (gameInfo.resultDetails != NULL &&
11320         gameInfo.resultDetails[0] != NULLCHAR) {
11321         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11322                 gameInfo.resultDetails);
11323     } else {
11324         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11325     }
11326
11327     fclose(f);
11328     return TRUE;
11329 }
11330
11331 /* Save the current game to open file f and close the file */
11332 int
11333 SaveGame(f, dummy, dummy2)
11334      FILE *f;
11335      int dummy;
11336      char *dummy2;
11337 {
11338     if (gameMode == EditPosition) EditPositionDone(TRUE);
11339     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11340     if (appData.oldSaveStyle)
11341       return SaveGameOldStyle(f);
11342     else
11343       return SaveGamePGN(f);
11344 }
11345
11346 /* Save the current position to the given file */
11347 int
11348 SavePositionToFile(filename)
11349      char *filename;
11350 {
11351     FILE *f;
11352     char buf[MSG_SIZ];
11353
11354     if (strcmp(filename, "-") == 0) {
11355         return SavePosition(stdout, 0, NULL);
11356     } else {
11357         f = fopen(filename, "a");
11358         if (f == NULL) {
11359             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11360             DisplayError(buf, errno);
11361             return FALSE;
11362         } else {
11363             SavePosition(f, 0, NULL);
11364             return TRUE;
11365         }
11366     }
11367 }
11368
11369 /* Save the current position to the given open file and close the file */
11370 int
11371 SavePosition(f, dummy, dummy2)
11372      FILE *f;
11373      int dummy;
11374      char *dummy2;
11375 {
11376     time_t tm;
11377     char *fen;
11378
11379     if (gameMode == EditPosition) EditPositionDone(TRUE);
11380     if (appData.oldSaveStyle) {
11381         tm = time((time_t *) NULL);
11382
11383         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11384         PrintOpponents(f);
11385         fprintf(f, "[--------------\n");
11386         PrintPosition(f, currentMove);
11387         fprintf(f, "--------------]\n");
11388     } else {
11389         fen = PositionToFEN(currentMove, NULL);
11390         fprintf(f, "%s\n", fen);
11391         free(fen);
11392     }
11393     fclose(f);
11394     return TRUE;
11395 }
11396
11397 void
11398 ReloadCmailMsgEvent(unregister)
11399      int unregister;
11400 {
11401 #if !WIN32
11402     static char *inFilename = NULL;
11403     static char *outFilename;
11404     int i;
11405     struct stat inbuf, outbuf;
11406     int status;
11407
11408     /* Any registered moves are unregistered if unregister is set, */
11409     /* i.e. invoked by the signal handler */
11410     if (unregister) {
11411         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11412             cmailMoveRegistered[i] = FALSE;
11413             if (cmailCommentList[i] != NULL) {
11414                 free(cmailCommentList[i]);
11415                 cmailCommentList[i] = NULL;
11416             }
11417         }
11418         nCmailMovesRegistered = 0;
11419     }
11420
11421     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11422         cmailResult[i] = CMAIL_NOT_RESULT;
11423     }
11424     nCmailResults = 0;
11425
11426     if (inFilename == NULL) {
11427         /* Because the filenames are static they only get malloced once  */
11428         /* and they never get freed                                      */
11429         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11430         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11431
11432         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11433         sprintf(outFilename, "%s.out", appData.cmailGameName);
11434     }
11435
11436     status = stat(outFilename, &outbuf);
11437     if (status < 0) {
11438         cmailMailedMove = FALSE;
11439     } else {
11440         status = stat(inFilename, &inbuf);
11441         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11442     }
11443
11444     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11445        counts the games, notes how each one terminated, etc.
11446
11447        It would be nice to remove this kludge and instead gather all
11448        the information while building the game list.  (And to keep it
11449        in the game list nodes instead of having a bunch of fixed-size
11450        parallel arrays.)  Note this will require getting each game's
11451        termination from the PGN tags, as the game list builder does
11452        not process the game moves.  --mann
11453        */
11454     cmailMsgLoaded = TRUE;
11455     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11456
11457     /* Load first game in the file or popup game menu */
11458     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11459
11460 #endif /* !WIN32 */
11461     return;
11462 }
11463
11464 int
11465 RegisterMove()
11466 {
11467     FILE *f;
11468     char string[MSG_SIZ];
11469
11470     if (   cmailMailedMove
11471         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11472         return TRUE;            /* Allow free viewing  */
11473     }
11474
11475     /* Unregister move to ensure that we don't leave RegisterMove        */
11476     /* with the move registered when the conditions for registering no   */
11477     /* longer hold                                                       */
11478     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11479         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11480         nCmailMovesRegistered --;
11481
11482         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11483           {
11484               free(cmailCommentList[lastLoadGameNumber - 1]);
11485               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11486           }
11487     }
11488
11489     if (cmailOldMove == -1) {
11490         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11491         return FALSE;
11492     }
11493
11494     if (currentMove > cmailOldMove + 1) {
11495         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11496         return FALSE;
11497     }
11498
11499     if (currentMove < cmailOldMove) {
11500         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11501         return FALSE;
11502     }
11503
11504     if (forwardMostMove > currentMove) {
11505         /* Silently truncate extra moves */
11506         TruncateGame();
11507     }
11508
11509     if (   (currentMove == cmailOldMove + 1)
11510         || (   (currentMove == cmailOldMove)
11511             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11512                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11513         if (gameInfo.result != GameUnfinished) {
11514             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11515         }
11516
11517         if (commentList[currentMove] != NULL) {
11518             cmailCommentList[lastLoadGameNumber - 1]
11519               = StrSave(commentList[currentMove]);
11520         }
11521         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11522
11523         if (appData.debugMode)
11524           fprintf(debugFP, "Saving %s for game %d\n",
11525                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11526
11527         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11528
11529         f = fopen(string, "w");
11530         if (appData.oldSaveStyle) {
11531             SaveGameOldStyle(f); /* also closes the file */
11532
11533             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11534             f = fopen(string, "w");
11535             SavePosition(f, 0, NULL); /* also closes the file */
11536         } else {
11537             fprintf(f, "{--------------\n");
11538             PrintPosition(f, currentMove);
11539             fprintf(f, "--------------}\n\n");
11540
11541             SaveGame(f, 0, NULL); /* also closes the file*/
11542         }
11543
11544         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11545         nCmailMovesRegistered ++;
11546     } else if (nCmailGames == 1) {
11547         DisplayError(_("You have not made a move yet"), 0);
11548         return FALSE;
11549     }
11550
11551     return TRUE;
11552 }
11553
11554 void
11555 MailMoveEvent()
11556 {
11557 #if !WIN32
11558     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11559     FILE *commandOutput;
11560     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11561     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11562     int nBuffers;
11563     int i;
11564     int archived;
11565     char *arcDir;
11566
11567     if (! cmailMsgLoaded) {
11568         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11569         return;
11570     }
11571
11572     if (nCmailGames == nCmailResults) {
11573         DisplayError(_("No unfinished games"), 0);
11574         return;
11575     }
11576
11577 #if CMAIL_PROHIBIT_REMAIL
11578     if (cmailMailedMove) {
11579       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);
11580         DisplayError(msg, 0);
11581         return;
11582     }
11583 #endif
11584
11585     if (! (cmailMailedMove || RegisterMove())) return;
11586
11587     if (   cmailMailedMove
11588         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11589       snprintf(string, MSG_SIZ, partCommandString,
11590                appData.debugMode ? " -v" : "", appData.cmailGameName);
11591         commandOutput = popen(string, "r");
11592
11593         if (commandOutput == NULL) {
11594             DisplayError(_("Failed to invoke cmail"), 0);
11595         } else {
11596             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11597                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11598             }
11599             if (nBuffers > 1) {
11600                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11601                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11602                 nBytes = MSG_SIZ - 1;
11603             } else {
11604                 (void) memcpy(msg, buffer, nBytes);
11605             }
11606             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11607
11608             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11609                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11610
11611                 archived = TRUE;
11612                 for (i = 0; i < nCmailGames; i ++) {
11613                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11614                         archived = FALSE;
11615                     }
11616                 }
11617                 if (   archived
11618                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11619                         != NULL)) {
11620                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11621                            arcDir,
11622                            appData.cmailGameName,
11623                            gameInfo.date);
11624                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11625                     cmailMsgLoaded = FALSE;
11626                 }
11627             }
11628
11629             DisplayInformation(msg);
11630             pclose(commandOutput);
11631         }
11632     } else {
11633         if ((*cmailMsg) != '\0') {
11634             DisplayInformation(cmailMsg);
11635         }
11636     }
11637
11638     return;
11639 #endif /* !WIN32 */
11640 }
11641
11642 char *
11643 CmailMsg()
11644 {
11645 #if WIN32
11646     return NULL;
11647 #else
11648     int  prependComma = 0;
11649     char number[5];
11650     char string[MSG_SIZ];       /* Space for game-list */
11651     int  i;
11652
11653     if (!cmailMsgLoaded) return "";
11654
11655     if (cmailMailedMove) {
11656       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11657     } else {
11658         /* Create a list of games left */
11659       snprintf(string, MSG_SIZ, "[");
11660         for (i = 0; i < nCmailGames; i ++) {
11661             if (! (   cmailMoveRegistered[i]
11662                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11663                 if (prependComma) {
11664                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11665                 } else {
11666                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11667                     prependComma = 1;
11668                 }
11669
11670                 strcat(string, number);
11671             }
11672         }
11673         strcat(string, "]");
11674
11675         if (nCmailMovesRegistered + nCmailResults == 0) {
11676             switch (nCmailGames) {
11677               case 1:
11678                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11679                 break;
11680
11681               case 2:
11682                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11683                 break;
11684
11685               default:
11686                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11687                          nCmailGames);
11688                 break;
11689             }
11690         } else {
11691             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11692               case 1:
11693                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11694                          string);
11695                 break;
11696
11697               case 0:
11698                 if (nCmailResults == nCmailGames) {
11699                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11700                 } else {
11701                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11702                 }
11703                 break;
11704
11705               default:
11706                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11707                          string);
11708             }
11709         }
11710     }
11711     return cmailMsg;
11712 #endif /* WIN32 */
11713 }
11714
11715 void
11716 ResetGameEvent()
11717 {
11718     if (gameMode == Training)
11719       SetTrainingModeOff();
11720
11721     Reset(TRUE, TRUE);
11722     cmailMsgLoaded = FALSE;
11723     if (appData.icsActive) {
11724       SendToICS(ics_prefix);
11725       SendToICS("refresh\n");
11726     }
11727 }
11728
11729 void
11730 ExitEvent(status)
11731      int status;
11732 {
11733     exiting++;
11734     if (exiting > 2) {
11735       /* Give up on clean exit */
11736       exit(status);
11737     }
11738     if (exiting > 1) {
11739       /* Keep trying for clean exit */
11740       return;
11741     }
11742
11743     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11744
11745     if (telnetISR != NULL) {
11746       RemoveInputSource(telnetISR);
11747     }
11748     if (icsPR != NoProc) {
11749       DestroyChildProcess(icsPR, TRUE);
11750     }
11751
11752     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11753     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11754
11755     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11756     /* make sure this other one finishes before killing it!                  */
11757     if(endingGame) { int count = 0;
11758         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11759         while(endingGame && count++ < 10) DoSleep(1);
11760         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11761     }
11762
11763     /* Kill off chess programs */
11764     if (first.pr != NoProc) {
11765         ExitAnalyzeMode();
11766
11767         DoSleep( appData.delayBeforeQuit );
11768         SendToProgram("quit\n", &first);
11769         DoSleep( appData.delayAfterQuit );
11770         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11771     }
11772     if (second.pr != NoProc) {
11773         DoSleep( appData.delayBeforeQuit );
11774         SendToProgram("quit\n", &second);
11775         DoSleep( appData.delayAfterQuit );
11776         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11777     }
11778     if (first.isr != NULL) {
11779         RemoveInputSource(first.isr);
11780     }
11781     if (second.isr != NULL) {
11782         RemoveInputSource(second.isr);
11783     }
11784
11785     ShutDownFrontEnd();
11786     exit(status);
11787 }
11788
11789 void
11790 PauseEvent()
11791 {
11792     if (appData.debugMode)
11793         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11794     if (pausing) {
11795         pausing = FALSE;
11796         ModeHighlight();
11797         if (gameMode == MachinePlaysWhite ||
11798             gameMode == MachinePlaysBlack) {
11799             StartClocks();
11800         } else {
11801             DisplayBothClocks();
11802         }
11803         if (gameMode == PlayFromGameFile) {
11804             if (appData.timeDelay >= 0)
11805                 AutoPlayGameLoop();
11806         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11807             Reset(FALSE, TRUE);
11808             SendToICS(ics_prefix);
11809             SendToICS("refresh\n");
11810         } else if (currentMove < forwardMostMove) {
11811             ForwardInner(forwardMostMove);
11812         }
11813         pauseExamInvalid = FALSE;
11814     } else {
11815         switch (gameMode) {
11816           default:
11817             return;
11818           case IcsExamining:
11819             pauseExamForwardMostMove = forwardMostMove;
11820             pauseExamInvalid = FALSE;
11821             /* fall through */
11822           case IcsObserving:
11823           case IcsPlayingWhite:
11824           case IcsPlayingBlack:
11825             pausing = TRUE;
11826             ModeHighlight();
11827             return;
11828           case PlayFromGameFile:
11829             (void) StopLoadGameTimer();
11830             pausing = TRUE;
11831             ModeHighlight();
11832             break;
11833           case BeginningOfGame:
11834             if (appData.icsActive) return;
11835             /* else fall through */
11836           case MachinePlaysWhite:
11837           case MachinePlaysBlack:
11838           case TwoMachinesPlay:
11839             if (forwardMostMove == 0)
11840               return;           /* don't pause if no one has moved */
11841             if ((gameMode == MachinePlaysWhite &&
11842                  !WhiteOnMove(forwardMostMove)) ||
11843                 (gameMode == MachinePlaysBlack &&
11844                  WhiteOnMove(forwardMostMove))) {
11845                 StopClocks();
11846             }
11847             pausing = TRUE;
11848             ModeHighlight();
11849             break;
11850         }
11851     }
11852 }
11853
11854 void
11855 EditCommentEvent()
11856 {
11857     char title[MSG_SIZ];
11858
11859     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11860       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11861     } else {
11862       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11863                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11864                parseList[currentMove - 1]);
11865     }
11866
11867     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11868 }
11869
11870
11871 void
11872 EditTagsEvent()
11873 {
11874     char *tags = PGNTags(&gameInfo);
11875     EditTagsPopUp(tags, NULL);
11876     free(tags);
11877 }
11878
11879 void
11880 AnalyzeModeEvent()
11881 {
11882     if (appData.noChessProgram || gameMode == AnalyzeMode)
11883       return;
11884
11885     if (gameMode != AnalyzeFile) {
11886         if (!appData.icsEngineAnalyze) {
11887                EditGameEvent();
11888                if (gameMode != EditGame) return;
11889         }
11890         ResurrectChessProgram();
11891         SendToProgram("analyze\n", &first);
11892         first.analyzing = TRUE;
11893         /*first.maybeThinking = TRUE;*/
11894         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11895         EngineOutputPopUp();
11896     }
11897     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11898     pausing = FALSE;
11899     ModeHighlight();
11900     SetGameInfo();
11901
11902     StartAnalysisClock();
11903     GetTimeMark(&lastNodeCountTime);
11904     lastNodeCount = 0;
11905 }
11906
11907 void
11908 AnalyzeFileEvent()
11909 {
11910     if (appData.noChessProgram || gameMode == AnalyzeFile)
11911       return;
11912
11913     if (gameMode != AnalyzeMode) {
11914         EditGameEvent();
11915         if (gameMode != EditGame) return;
11916         ResurrectChessProgram();
11917         SendToProgram("analyze\n", &first);
11918         first.analyzing = TRUE;
11919         /*first.maybeThinking = TRUE;*/
11920         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11921         EngineOutputPopUp();
11922     }
11923     gameMode = AnalyzeFile;
11924     pausing = FALSE;
11925     ModeHighlight();
11926     SetGameInfo();
11927
11928     StartAnalysisClock();
11929     GetTimeMark(&lastNodeCountTime);
11930     lastNodeCount = 0;
11931 }
11932
11933 void
11934 MachineWhiteEvent()
11935 {
11936     char buf[MSG_SIZ];
11937     char *bookHit = NULL;
11938
11939     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11940       return;
11941
11942
11943     if (gameMode == PlayFromGameFile ||
11944         gameMode == TwoMachinesPlay  ||
11945         gameMode == Training         ||
11946         gameMode == AnalyzeMode      ||
11947         gameMode == EndOfGame)
11948         EditGameEvent();
11949
11950     if (gameMode == EditPosition)
11951         EditPositionDone(TRUE);
11952
11953     if (!WhiteOnMove(currentMove)) {
11954         DisplayError(_("It is not White's turn"), 0);
11955         return;
11956     }
11957
11958     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11959       ExitAnalyzeMode();
11960
11961     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11962         gameMode == AnalyzeFile)
11963         TruncateGame();
11964
11965     ResurrectChessProgram();    /* in case it isn't running */
11966     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11967         gameMode = MachinePlaysWhite;
11968         ResetClocks();
11969     } else
11970     gameMode = MachinePlaysWhite;
11971     pausing = FALSE;
11972     ModeHighlight();
11973     SetGameInfo();
11974     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11975     DisplayTitle(buf);
11976     if (first.sendName) {
11977       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11978       SendToProgram(buf, &first);
11979     }
11980     if (first.sendTime) {
11981       if (first.useColors) {
11982         SendToProgram("black\n", &first); /*gnu kludge*/
11983       }
11984       SendTimeRemaining(&first, TRUE);
11985     }
11986     if (first.useColors) {
11987       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11988     }
11989     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11990     SetMachineThinkingEnables();
11991     first.maybeThinking = TRUE;
11992     StartClocks();
11993     firstMove = FALSE;
11994
11995     if (appData.autoFlipView && !flipView) {
11996       flipView = !flipView;
11997       DrawPosition(FALSE, NULL);
11998       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11999     }
12000
12001     if(bookHit) { // [HGM] book: simulate book reply
12002         static char bookMove[MSG_SIZ]; // a bit generous?
12003
12004         programStats.nodes = programStats.depth = programStats.time =
12005         programStats.score = programStats.got_only_move = 0;
12006         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12007
12008         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12009         strcat(bookMove, bookHit);
12010         HandleMachineMove(bookMove, &first);
12011     }
12012 }
12013
12014 void
12015 MachineBlackEvent()
12016 {
12017   char buf[MSG_SIZ];
12018   char *bookHit = NULL;
12019
12020     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12021         return;
12022
12023
12024     if (gameMode == PlayFromGameFile ||
12025         gameMode == TwoMachinesPlay  ||
12026         gameMode == Training         ||
12027         gameMode == AnalyzeMode      ||
12028         gameMode == EndOfGame)
12029         EditGameEvent();
12030
12031     if (gameMode == EditPosition)
12032         EditPositionDone(TRUE);
12033
12034     if (WhiteOnMove(currentMove)) {
12035         DisplayError(_("It is not Black's turn"), 0);
12036         return;
12037     }
12038
12039     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12040       ExitAnalyzeMode();
12041
12042     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12043         gameMode == AnalyzeFile)
12044         TruncateGame();
12045
12046     ResurrectChessProgram();    /* in case it isn't running */
12047     gameMode = MachinePlaysBlack;
12048     pausing = FALSE;
12049     ModeHighlight();
12050     SetGameInfo();
12051     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12052     DisplayTitle(buf);
12053     if (first.sendName) {
12054       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12055       SendToProgram(buf, &first);
12056     }
12057     if (first.sendTime) {
12058       if (first.useColors) {
12059         SendToProgram("white\n", &first); /*gnu kludge*/
12060       }
12061       SendTimeRemaining(&first, FALSE);
12062     }
12063     if (first.useColors) {
12064       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12065     }
12066     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12067     SetMachineThinkingEnables();
12068     first.maybeThinking = TRUE;
12069     StartClocks();
12070
12071     if (appData.autoFlipView && flipView) {
12072       flipView = !flipView;
12073       DrawPosition(FALSE, NULL);
12074       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12075     }
12076     if(bookHit) { // [HGM] book: simulate book reply
12077         static char bookMove[MSG_SIZ]; // a bit generous?
12078
12079         programStats.nodes = programStats.depth = programStats.time =
12080         programStats.score = programStats.got_only_move = 0;
12081         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12082
12083         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12084         strcat(bookMove, bookHit);
12085         HandleMachineMove(bookMove, &first);
12086     }
12087 }
12088
12089
12090 void
12091 DisplayTwoMachinesTitle()
12092 {
12093     char buf[MSG_SIZ];
12094     if (appData.matchGames > 0) {
12095         if (first.twoMachinesColor[0] == 'w') {
12096           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12097                    gameInfo.white, gameInfo.black,
12098                    first.matchWins, second.matchWins,
12099                    matchGame - 1 - (first.matchWins + second.matchWins));
12100         } else {
12101           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12102                    gameInfo.white, gameInfo.black,
12103                    second.matchWins, first.matchWins,
12104                    matchGame - 1 - (first.matchWins + second.matchWins));
12105         }
12106     } else {
12107       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12108     }
12109     DisplayTitle(buf);
12110 }
12111
12112 void
12113 SettingsMenuIfReady()
12114 {
12115   if (second.lastPing != second.lastPong) {
12116     DisplayMessage("", _("Waiting for second chess program"));
12117     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12118     return;
12119   }
12120   ThawUI();
12121   DisplayMessage("", "");
12122   SettingsPopUp(&second);
12123 }
12124
12125 int
12126 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12127 {
12128     char buf[MSG_SIZ];
12129     if (cps->pr == NULL) {
12130         StartChessProgram(cps);
12131         if (cps->protocolVersion == 1) {
12132           retry();
12133         } else {
12134           /* kludge: allow timeout for initial "feature" command */
12135           FreezeUI();
12136           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12137           DisplayMessage("", buf);
12138           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12139         }
12140         return 1;
12141     }
12142     return 0;
12143 }
12144
12145 void
12146 TwoMachinesEvent P((void))
12147 {
12148     int i;
12149     char buf[MSG_SIZ];
12150     ChessProgramState *onmove;
12151     char *bookHit = NULL;
12152     static int stalling = 0;
12153
12154     if (appData.noChessProgram) return;
12155
12156     switch (gameMode) {
12157       case TwoMachinesPlay:
12158         return;
12159       case MachinePlaysWhite:
12160       case MachinePlaysBlack:
12161         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12162             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12163             return;
12164         }
12165         /* fall through */
12166       case BeginningOfGame:
12167       case PlayFromGameFile:
12168       case EndOfGame:
12169         EditGameEvent();
12170         if (gameMode != EditGame) return;
12171         break;
12172       case EditPosition:
12173         EditPositionDone(TRUE);
12174         break;
12175       case AnalyzeMode:
12176       case AnalyzeFile:
12177         ExitAnalyzeMode();
12178         break;
12179       case EditGame:
12180       default:
12181         break;
12182     }
12183
12184 //    forwardMostMove = currentMove;
12185     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12186     ResurrectChessProgram();    /* in case first program isn't running */
12187
12188     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12189     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12190       DisplayMessage("", _("Waiting for first chess program"));
12191       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12192       return;
12193     }
12194     if(!stalling) {
12195       InitChessProgram(&second, FALSE);
12196       SendToProgram("force\n", &second);
12197     }
12198     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12199       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12200       stalling = 1;
12201       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12202       return;
12203     }
12204     stalling = 0;
12205     DisplayMessage("", "");
12206     if (startedFromSetupPosition) {
12207         SendBoard(&second, backwardMostMove);
12208     if (appData.debugMode) {
12209         fprintf(debugFP, "Two Machines\n");
12210     }
12211     }
12212     for (i = backwardMostMove; i < forwardMostMove; i++) {
12213         SendMoveToProgram(i, &second);
12214     }
12215
12216     gameMode = TwoMachinesPlay;
12217     pausing = FALSE;
12218     ModeHighlight();
12219     SetGameInfo();
12220     DisplayTwoMachinesTitle();
12221     firstMove = TRUE;
12222     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12223         onmove = &first;
12224     } else {
12225         onmove = &second;
12226     }
12227
12228     SendToProgram(first.computerString, &first);
12229     if (first.sendName) {
12230       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12231       SendToProgram(buf, &first);
12232     }
12233     SendToProgram(second.computerString, &second);
12234     if (second.sendName) {
12235       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12236       SendToProgram(buf, &second);
12237     }
12238
12239     ResetClocks();
12240     if (!first.sendTime || !second.sendTime) {
12241         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12242         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12243     }
12244     if (onmove->sendTime) {
12245       if (onmove->useColors) {
12246         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12247       }
12248       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12249     }
12250     if (onmove->useColors) {
12251       SendToProgram(onmove->twoMachinesColor, onmove);
12252     }
12253     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12254 //    SendToProgram("go\n", onmove);
12255     onmove->maybeThinking = TRUE;
12256     SetMachineThinkingEnables();
12257
12258     StartClocks();
12259
12260     if(bookHit) { // [HGM] book: simulate book reply
12261         static char bookMove[MSG_SIZ]; // a bit generous?
12262
12263         programStats.nodes = programStats.depth = programStats.time =
12264         programStats.score = programStats.got_only_move = 0;
12265         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12266
12267         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12268         strcat(bookMove, bookHit);
12269         savedMessage = bookMove; // args for deferred call
12270         savedState = onmove;
12271         ScheduleDelayedEvent(DeferredBookMove, 1);
12272     }
12273 }
12274
12275 void
12276 TrainingEvent()
12277 {
12278     if (gameMode == Training) {
12279       SetTrainingModeOff();
12280       gameMode = PlayFromGameFile;
12281       DisplayMessage("", _("Training mode off"));
12282     } else {
12283       gameMode = Training;
12284       animateTraining = appData.animate;
12285
12286       /* make sure we are not already at the end of the game */
12287       if (currentMove < forwardMostMove) {
12288         SetTrainingModeOn();
12289         DisplayMessage("", _("Training mode on"));
12290       } else {
12291         gameMode = PlayFromGameFile;
12292         DisplayError(_("Already at end of game"), 0);
12293       }
12294     }
12295     ModeHighlight();
12296 }
12297
12298 void
12299 IcsClientEvent()
12300 {
12301     if (!appData.icsActive) return;
12302     switch (gameMode) {
12303       case IcsPlayingWhite:
12304       case IcsPlayingBlack:
12305       case IcsObserving:
12306       case IcsIdle:
12307       case BeginningOfGame:
12308       case IcsExamining:
12309         return;
12310
12311       case EditGame:
12312         break;
12313
12314       case EditPosition:
12315         EditPositionDone(TRUE);
12316         break;
12317
12318       case AnalyzeMode:
12319       case AnalyzeFile:
12320         ExitAnalyzeMode();
12321         break;
12322
12323       default:
12324         EditGameEvent();
12325         break;
12326     }
12327
12328     gameMode = IcsIdle;
12329     ModeHighlight();
12330     return;
12331 }
12332
12333
12334 void
12335 EditGameEvent()
12336 {
12337     int i;
12338
12339     switch (gameMode) {
12340       case Training:
12341         SetTrainingModeOff();
12342         break;
12343       case MachinePlaysWhite:
12344       case MachinePlaysBlack:
12345       case BeginningOfGame:
12346         SendToProgram("force\n", &first);
12347         SetUserThinkingEnables();
12348         break;
12349       case PlayFromGameFile:
12350         (void) StopLoadGameTimer();
12351         if (gameFileFP != NULL) {
12352             gameFileFP = NULL;
12353         }
12354         break;
12355       case EditPosition:
12356         EditPositionDone(TRUE);
12357         break;
12358       case AnalyzeMode:
12359       case AnalyzeFile:
12360         ExitAnalyzeMode();
12361         SendToProgram("force\n", &first);
12362         break;
12363       case TwoMachinesPlay:
12364         GameEnds(EndOfFile, NULL, GE_PLAYER);
12365         ResurrectChessProgram();
12366         SetUserThinkingEnables();
12367         break;
12368       case EndOfGame:
12369         ResurrectChessProgram();
12370         break;
12371       case IcsPlayingBlack:
12372       case IcsPlayingWhite:
12373         DisplayError(_("Warning: You are still playing a game"), 0);
12374         break;
12375       case IcsObserving:
12376         DisplayError(_("Warning: You are still observing a game"), 0);
12377         break;
12378       case IcsExamining:
12379         DisplayError(_("Warning: You are still examining a game"), 0);
12380         break;
12381       case IcsIdle:
12382         break;
12383       case EditGame:
12384       default:
12385         return;
12386     }
12387
12388     pausing = FALSE;
12389     StopClocks();
12390     first.offeredDraw = second.offeredDraw = 0;
12391
12392     if (gameMode == PlayFromGameFile) {
12393         whiteTimeRemaining = timeRemaining[0][currentMove];
12394         blackTimeRemaining = timeRemaining[1][currentMove];
12395         DisplayTitle("");
12396     }
12397
12398     if (gameMode == MachinePlaysWhite ||
12399         gameMode == MachinePlaysBlack ||
12400         gameMode == TwoMachinesPlay ||
12401         gameMode == EndOfGame) {
12402         i = forwardMostMove;
12403         while (i > currentMove) {
12404             SendToProgram("undo\n", &first);
12405             i--;
12406         }
12407         whiteTimeRemaining = timeRemaining[0][currentMove];
12408         blackTimeRemaining = timeRemaining[1][currentMove];
12409         DisplayBothClocks();
12410         if (whiteFlag || blackFlag) {
12411             whiteFlag = blackFlag = 0;
12412         }
12413         DisplayTitle("");
12414     }
12415
12416     gameMode = EditGame;
12417     ModeHighlight();
12418     SetGameInfo();
12419 }
12420
12421
12422 void
12423 EditPositionEvent()
12424 {
12425     if (gameMode == EditPosition) {
12426         EditGameEvent();
12427         return;
12428     }
12429
12430     EditGameEvent();
12431     if (gameMode != EditGame) return;
12432
12433     gameMode = EditPosition;
12434     ModeHighlight();
12435     SetGameInfo();
12436     if (currentMove > 0)
12437       CopyBoard(boards[0], boards[currentMove]);
12438
12439     blackPlaysFirst = !WhiteOnMove(currentMove);
12440     ResetClocks();
12441     currentMove = forwardMostMove = backwardMostMove = 0;
12442     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12443     DisplayMove(-1);
12444 }
12445
12446 void
12447 ExitAnalyzeMode()
12448 {
12449     /* [DM] icsEngineAnalyze - possible call from other functions */
12450     if (appData.icsEngineAnalyze) {
12451         appData.icsEngineAnalyze = FALSE;
12452
12453         DisplayMessage("",_("Close ICS engine analyze..."));
12454     }
12455     if (first.analysisSupport && first.analyzing) {
12456       SendToProgram("exit\n", &first);
12457       first.analyzing = FALSE;
12458     }
12459     thinkOutput[0] = NULLCHAR;
12460 }
12461
12462 void
12463 EditPositionDone(Boolean fakeRights)
12464 {
12465     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12466
12467     startedFromSetupPosition = TRUE;
12468     InitChessProgram(&first, FALSE);
12469     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12470       boards[0][EP_STATUS] = EP_NONE;
12471       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12472     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12473         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12474         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12475       } else boards[0][CASTLING][2] = NoRights;
12476     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12477         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12478         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12479       } else boards[0][CASTLING][5] = NoRights;
12480     }
12481     SendToProgram("force\n", &first);
12482     if (blackPlaysFirst) {
12483         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12484         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12485         currentMove = forwardMostMove = backwardMostMove = 1;
12486         CopyBoard(boards[1], boards[0]);
12487     } else {
12488         currentMove = forwardMostMove = backwardMostMove = 0;
12489     }
12490     SendBoard(&first, forwardMostMove);
12491     if (appData.debugMode) {
12492         fprintf(debugFP, "EditPosDone\n");
12493     }
12494     DisplayTitle("");
12495     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12496     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12497     gameMode = EditGame;
12498     ModeHighlight();
12499     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12500     ClearHighlights(); /* [AS] */
12501 }
12502
12503 /* Pause for `ms' milliseconds */
12504 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12505 void
12506 TimeDelay(ms)
12507      long ms;
12508 {
12509     TimeMark m1, m2;
12510
12511     GetTimeMark(&m1);
12512     do {
12513         GetTimeMark(&m2);
12514     } while (SubtractTimeMarks(&m2, &m1) < ms);
12515 }
12516
12517 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12518 void
12519 SendMultiLineToICS(buf)
12520      char *buf;
12521 {
12522     char temp[MSG_SIZ+1], *p;
12523     int len;
12524
12525     len = strlen(buf);
12526     if (len > MSG_SIZ)
12527       len = MSG_SIZ;
12528
12529     strncpy(temp, buf, len);
12530     temp[len] = 0;
12531
12532     p = temp;
12533     while (*p) {
12534         if (*p == '\n' || *p == '\r')
12535           *p = ' ';
12536         ++p;
12537     }
12538
12539     strcat(temp, "\n");
12540     SendToICS(temp);
12541     SendToPlayer(temp, strlen(temp));
12542 }
12543
12544 void
12545 SetWhiteToPlayEvent()
12546 {
12547     if (gameMode == EditPosition) {
12548         blackPlaysFirst = FALSE;
12549         DisplayBothClocks();    /* works because currentMove is 0 */
12550     } else if (gameMode == IcsExamining) {
12551         SendToICS(ics_prefix);
12552         SendToICS("tomove white\n");
12553     }
12554 }
12555
12556 void
12557 SetBlackToPlayEvent()
12558 {
12559     if (gameMode == EditPosition) {
12560         blackPlaysFirst = TRUE;
12561         currentMove = 1;        /* kludge */
12562         DisplayBothClocks();
12563         currentMove = 0;
12564     } else if (gameMode == IcsExamining) {
12565         SendToICS(ics_prefix);
12566         SendToICS("tomove black\n");
12567     }
12568 }
12569
12570 void
12571 EditPositionMenuEvent(selection, x, y)
12572      ChessSquare selection;
12573      int x, y;
12574 {
12575     char buf[MSG_SIZ];
12576     ChessSquare piece = boards[0][y][x];
12577
12578     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12579
12580     switch (selection) {
12581       case ClearBoard:
12582         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12583             SendToICS(ics_prefix);
12584             SendToICS("bsetup clear\n");
12585         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12586             SendToICS(ics_prefix);
12587             SendToICS("clearboard\n");
12588         } else {
12589             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12590                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12591                 for (y = 0; y < BOARD_HEIGHT; y++) {
12592                     if (gameMode == IcsExamining) {
12593                         if (boards[currentMove][y][x] != EmptySquare) {
12594                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12595                                     AAA + x, ONE + y);
12596                             SendToICS(buf);
12597                         }
12598                     } else {
12599                         boards[0][y][x] = p;
12600                     }
12601                 }
12602             }
12603         }
12604         if (gameMode == EditPosition) {
12605             DrawPosition(FALSE, boards[0]);
12606         }
12607         break;
12608
12609       case WhitePlay:
12610         SetWhiteToPlayEvent();
12611         break;
12612
12613       case BlackPlay:
12614         SetBlackToPlayEvent();
12615         break;
12616
12617       case EmptySquare:
12618         if (gameMode == IcsExamining) {
12619             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12620             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12621             SendToICS(buf);
12622         } else {
12623             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12624                 if(x == BOARD_LEFT-2) {
12625                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12626                     boards[0][y][1] = 0;
12627                 } else
12628                 if(x == BOARD_RGHT+1) {
12629                     if(y >= gameInfo.holdingsSize) break;
12630                     boards[0][y][BOARD_WIDTH-2] = 0;
12631                 } else break;
12632             }
12633             boards[0][y][x] = EmptySquare;
12634             DrawPosition(FALSE, boards[0]);
12635         }
12636         break;
12637
12638       case PromotePiece:
12639         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12640            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12641             selection = (ChessSquare) (PROMOTED piece);
12642         } else if(piece == EmptySquare) selection = WhiteSilver;
12643         else selection = (ChessSquare)((int)piece - 1);
12644         goto defaultlabel;
12645
12646       case DemotePiece:
12647         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12648            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12649             selection = (ChessSquare) (DEMOTED piece);
12650         } else if(piece == EmptySquare) selection = BlackSilver;
12651         else selection = (ChessSquare)((int)piece + 1);
12652         goto defaultlabel;
12653
12654       case WhiteQueen:
12655       case BlackQueen:
12656         if(gameInfo.variant == VariantShatranj ||
12657            gameInfo.variant == VariantXiangqi  ||
12658            gameInfo.variant == VariantCourier  ||
12659            gameInfo.variant == VariantMakruk     )
12660             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12661         goto defaultlabel;
12662
12663       case WhiteKing:
12664       case BlackKing:
12665         if(gameInfo.variant == VariantXiangqi)
12666             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12667         if(gameInfo.variant == VariantKnightmate)
12668             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12669       default:
12670         defaultlabel:
12671         if (gameMode == IcsExamining) {
12672             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12673             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12674                      PieceToChar(selection), AAA + x, ONE + y);
12675             SendToICS(buf);
12676         } else {
12677             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12678                 int n;
12679                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12680                     n = PieceToNumber(selection - BlackPawn);
12681                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12682                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12683                     boards[0][BOARD_HEIGHT-1-n][1]++;
12684                 } else
12685                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12686                     n = PieceToNumber(selection);
12687                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12688                     boards[0][n][BOARD_WIDTH-1] = selection;
12689                     boards[0][n][BOARD_WIDTH-2]++;
12690                 }
12691             } else
12692             boards[0][y][x] = selection;
12693             DrawPosition(TRUE, boards[0]);
12694         }
12695         break;
12696     }
12697 }
12698
12699
12700 void
12701 DropMenuEvent(selection, x, y)
12702      ChessSquare selection;
12703      int x, y;
12704 {
12705     ChessMove moveType;
12706
12707     switch (gameMode) {
12708       case IcsPlayingWhite:
12709       case MachinePlaysBlack:
12710         if (!WhiteOnMove(currentMove)) {
12711             DisplayMoveError(_("It is Black's turn"));
12712             return;
12713         }
12714         moveType = WhiteDrop;
12715         break;
12716       case IcsPlayingBlack:
12717       case MachinePlaysWhite:
12718         if (WhiteOnMove(currentMove)) {
12719             DisplayMoveError(_("It is White's turn"));
12720             return;
12721         }
12722         moveType = BlackDrop;
12723         break;
12724       case EditGame:
12725         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12726         break;
12727       default:
12728         return;
12729     }
12730
12731     if (moveType == BlackDrop && selection < BlackPawn) {
12732       selection = (ChessSquare) ((int) selection
12733                                  + (int) BlackPawn - (int) WhitePawn);
12734     }
12735     if (boards[currentMove][y][x] != EmptySquare) {
12736         DisplayMoveError(_("That square is occupied"));
12737         return;
12738     }
12739
12740     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12741 }
12742
12743 void
12744 AcceptEvent()
12745 {
12746     /* Accept a pending offer of any kind from opponent */
12747
12748     if (appData.icsActive) {
12749         SendToICS(ics_prefix);
12750         SendToICS("accept\n");
12751     } else if (cmailMsgLoaded) {
12752         if (currentMove == cmailOldMove &&
12753             commentList[cmailOldMove] != NULL &&
12754             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12755                    "Black offers a draw" : "White offers a draw")) {
12756             TruncateGame();
12757             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12758             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12759         } else {
12760             DisplayError(_("There is no pending offer on this move"), 0);
12761             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12762         }
12763     } else {
12764         /* Not used for offers from chess program */
12765     }
12766 }
12767
12768 void
12769 DeclineEvent()
12770 {
12771     /* Decline a pending offer of any kind from opponent */
12772
12773     if (appData.icsActive) {
12774         SendToICS(ics_prefix);
12775         SendToICS("decline\n");
12776     } else if (cmailMsgLoaded) {
12777         if (currentMove == cmailOldMove &&
12778             commentList[cmailOldMove] != NULL &&
12779             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12780                    "Black offers a draw" : "White offers a draw")) {
12781 #ifdef NOTDEF
12782             AppendComment(cmailOldMove, "Draw declined", TRUE);
12783             DisplayComment(cmailOldMove - 1, "Draw declined");
12784 #endif /*NOTDEF*/
12785         } else {
12786             DisplayError(_("There is no pending offer on this move"), 0);
12787         }
12788     } else {
12789         /* Not used for offers from chess program */
12790     }
12791 }
12792
12793 void
12794 RematchEvent()
12795 {
12796     /* Issue ICS rematch command */
12797     if (appData.icsActive) {
12798         SendToICS(ics_prefix);
12799         SendToICS("rematch\n");
12800     }
12801 }
12802
12803 void
12804 CallFlagEvent()
12805 {
12806     /* Call your opponent's flag (claim a win on time) */
12807     if (appData.icsActive) {
12808         SendToICS(ics_prefix);
12809         SendToICS("flag\n");
12810     } else {
12811         switch (gameMode) {
12812           default:
12813             return;
12814           case MachinePlaysWhite:
12815             if (whiteFlag) {
12816                 if (blackFlag)
12817                   GameEnds(GameIsDrawn, "Both players ran out of time",
12818                            GE_PLAYER);
12819                 else
12820                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12821             } else {
12822                 DisplayError(_("Your opponent is not out of time"), 0);
12823             }
12824             break;
12825           case MachinePlaysBlack:
12826             if (blackFlag) {
12827                 if (whiteFlag)
12828                   GameEnds(GameIsDrawn, "Both players ran out of time",
12829                            GE_PLAYER);
12830                 else
12831                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12832             } else {
12833                 DisplayError(_("Your opponent is not out of time"), 0);
12834             }
12835             break;
12836         }
12837     }
12838 }
12839
12840 void
12841 ClockClick(int which)
12842 {       // [HGM] code moved to back-end from winboard.c
12843         if(which) { // black clock
12844           if (gameMode == EditPosition || gameMode == IcsExamining) {
12845             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12846             SetBlackToPlayEvent();
12847           } else if (gameMode == EditGame || shiftKey) {
12848             AdjustClock(which, -1);
12849           } else if (gameMode == IcsPlayingWhite ||
12850                      gameMode == MachinePlaysBlack) {
12851             CallFlagEvent();
12852           }
12853         } else { // white clock
12854           if (gameMode == EditPosition || gameMode == IcsExamining) {
12855             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12856             SetWhiteToPlayEvent();
12857           } else if (gameMode == EditGame || shiftKey) {
12858             AdjustClock(which, -1);
12859           } else if (gameMode == IcsPlayingBlack ||
12860                    gameMode == MachinePlaysWhite) {
12861             CallFlagEvent();
12862           }
12863         }
12864 }
12865
12866 void
12867 DrawEvent()
12868 {
12869     /* Offer draw or accept pending draw offer from opponent */
12870
12871     if (appData.icsActive) {
12872         /* Note: tournament rules require draw offers to be
12873            made after you make your move but before you punch
12874            your clock.  Currently ICS doesn't let you do that;
12875            instead, you immediately punch your clock after making
12876            a move, but you can offer a draw at any time. */
12877
12878         SendToICS(ics_prefix);
12879         SendToICS("draw\n");
12880         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12881     } else if (cmailMsgLoaded) {
12882         if (currentMove == cmailOldMove &&
12883             commentList[cmailOldMove] != NULL &&
12884             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12885                    "Black offers a draw" : "White offers a draw")) {
12886             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12887             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12888         } else if (currentMove == cmailOldMove + 1) {
12889             char *offer = WhiteOnMove(cmailOldMove) ?
12890               "White offers a draw" : "Black offers a draw";
12891             AppendComment(currentMove, offer, TRUE);
12892             DisplayComment(currentMove - 1, offer);
12893             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12894         } else {
12895             DisplayError(_("You must make your move before offering a draw"), 0);
12896             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12897         }
12898     } else if (first.offeredDraw) {
12899         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12900     } else {
12901         if (first.sendDrawOffers) {
12902             SendToProgram("draw\n", &first);
12903             userOfferedDraw = TRUE;
12904         }
12905     }
12906 }
12907
12908 void
12909 AdjournEvent()
12910 {
12911     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12912
12913     if (appData.icsActive) {
12914         SendToICS(ics_prefix);
12915         SendToICS("adjourn\n");
12916     } else {
12917         /* Currently GNU Chess doesn't offer or accept Adjourns */
12918     }
12919 }
12920
12921
12922 void
12923 AbortEvent()
12924 {
12925     /* Offer Abort or accept pending Abort offer from opponent */
12926
12927     if (appData.icsActive) {
12928         SendToICS(ics_prefix);
12929         SendToICS("abort\n");
12930     } else {
12931         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12932     }
12933 }
12934
12935 void
12936 ResignEvent()
12937 {
12938     /* Resign.  You can do this even if it's not your turn. */
12939
12940     if (appData.icsActive) {
12941         SendToICS(ics_prefix);
12942         SendToICS("resign\n");
12943     } else {
12944         switch (gameMode) {
12945           case MachinePlaysWhite:
12946             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12947             break;
12948           case MachinePlaysBlack:
12949             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12950             break;
12951           case EditGame:
12952             if (cmailMsgLoaded) {
12953                 TruncateGame();
12954                 if (WhiteOnMove(cmailOldMove)) {
12955                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12956                 } else {
12957                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12958                 }
12959                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12960             }
12961             break;
12962           default:
12963             break;
12964         }
12965     }
12966 }
12967
12968
12969 void
12970 StopObservingEvent()
12971 {
12972     /* Stop observing current games */
12973     SendToICS(ics_prefix);
12974     SendToICS("unobserve\n");
12975 }
12976
12977 void
12978 StopExaminingEvent()
12979 {
12980     /* Stop observing current game */
12981     SendToICS(ics_prefix);
12982     SendToICS("unexamine\n");
12983 }
12984
12985 void
12986 ForwardInner(target)
12987      int target;
12988 {
12989     int limit;
12990
12991     if (appData.debugMode)
12992         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12993                 target, currentMove, forwardMostMove);
12994
12995     if (gameMode == EditPosition)
12996       return;
12997
12998     if (gameMode == PlayFromGameFile && !pausing)
12999       PauseEvent();
13000
13001     if (gameMode == IcsExamining && pausing)
13002       limit = pauseExamForwardMostMove;
13003     else
13004       limit = forwardMostMove;
13005
13006     if (target > limit) target = limit;
13007
13008     if (target > 0 && moveList[target - 1][0]) {
13009         int fromX, fromY, toX, toY;
13010         toX = moveList[target - 1][2] - AAA;
13011         toY = moveList[target - 1][3] - ONE;
13012         if (moveList[target - 1][1] == '@') {
13013             if (appData.highlightLastMove) {
13014                 SetHighlights(-1, -1, toX, toY);
13015             }
13016         } else {
13017             fromX = moveList[target - 1][0] - AAA;
13018             fromY = moveList[target - 1][1] - ONE;
13019             if (target == currentMove + 1) {
13020                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13021             }
13022             if (appData.highlightLastMove) {
13023                 SetHighlights(fromX, fromY, toX, toY);
13024             }
13025         }
13026     }
13027     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13028         gameMode == Training || gameMode == PlayFromGameFile ||
13029         gameMode == AnalyzeFile) {
13030         while (currentMove < target) {
13031             SendMoveToProgram(currentMove++, &first);
13032         }
13033     } else {
13034         currentMove = target;
13035     }
13036
13037     if (gameMode == EditGame || gameMode == EndOfGame) {
13038         whiteTimeRemaining = timeRemaining[0][currentMove];
13039         blackTimeRemaining = timeRemaining[1][currentMove];
13040     }
13041     DisplayBothClocks();
13042     DisplayMove(currentMove - 1);
13043     DrawPosition(FALSE, boards[currentMove]);
13044     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13045     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13046         DisplayComment(currentMove - 1, commentList[currentMove]);
13047     }
13048 }
13049
13050
13051 void
13052 ForwardEvent()
13053 {
13054     if (gameMode == IcsExamining && !pausing) {
13055         SendToICS(ics_prefix);
13056         SendToICS("forward\n");
13057     } else {
13058         ForwardInner(currentMove + 1);
13059     }
13060 }
13061
13062 void
13063 ToEndEvent()
13064 {
13065     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13066         /* to optimze, we temporarily turn off analysis mode while we feed
13067          * the remaining moves to the engine. Otherwise we get analysis output
13068          * after each move.
13069          */
13070         if (first.analysisSupport) {
13071           SendToProgram("exit\nforce\n", &first);
13072           first.analyzing = FALSE;
13073         }
13074     }
13075
13076     if (gameMode == IcsExamining && !pausing) {
13077         SendToICS(ics_prefix);
13078         SendToICS("forward 999999\n");
13079     } else {
13080         ForwardInner(forwardMostMove);
13081     }
13082
13083     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13084         /* we have fed all the moves, so reactivate analysis mode */
13085         SendToProgram("analyze\n", &first);
13086         first.analyzing = TRUE;
13087         /*first.maybeThinking = TRUE;*/
13088         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13089     }
13090 }
13091
13092 void
13093 BackwardInner(target)
13094      int target;
13095 {
13096     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13097
13098     if (appData.debugMode)
13099         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13100                 target, currentMove, forwardMostMove);
13101
13102     if (gameMode == EditPosition) return;
13103     if (currentMove <= backwardMostMove) {
13104         ClearHighlights();
13105         DrawPosition(full_redraw, boards[currentMove]);
13106         return;
13107     }
13108     if (gameMode == PlayFromGameFile && !pausing)
13109       PauseEvent();
13110
13111     if (moveList[target][0]) {
13112         int fromX, fromY, toX, toY;
13113         toX = moveList[target][2] - AAA;
13114         toY = moveList[target][3] - ONE;
13115         if (moveList[target][1] == '@') {
13116             if (appData.highlightLastMove) {
13117                 SetHighlights(-1, -1, toX, toY);
13118             }
13119         } else {
13120             fromX = moveList[target][0] - AAA;
13121             fromY = moveList[target][1] - ONE;
13122             if (target == currentMove - 1) {
13123                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13124             }
13125             if (appData.highlightLastMove) {
13126                 SetHighlights(fromX, fromY, toX, toY);
13127             }
13128         }
13129     }
13130     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13131         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13132         while (currentMove > target) {
13133             SendToProgram("undo\n", &first);
13134             currentMove--;
13135         }
13136     } else {
13137         currentMove = target;
13138     }
13139
13140     if (gameMode == EditGame || gameMode == EndOfGame) {
13141         whiteTimeRemaining = timeRemaining[0][currentMove];
13142         blackTimeRemaining = timeRemaining[1][currentMove];
13143     }
13144     DisplayBothClocks();
13145     DisplayMove(currentMove - 1);
13146     DrawPosition(full_redraw, boards[currentMove]);
13147     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13148     // [HGM] PV info: routine tests if comment empty
13149     DisplayComment(currentMove - 1, commentList[currentMove]);
13150 }
13151
13152 void
13153 BackwardEvent()
13154 {
13155     if (gameMode == IcsExamining && !pausing) {
13156         SendToICS(ics_prefix);
13157         SendToICS("backward\n");
13158     } else {
13159         BackwardInner(currentMove - 1);
13160     }
13161 }
13162
13163 void
13164 ToStartEvent()
13165 {
13166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13167         /* to optimize, we temporarily turn off analysis mode while we undo
13168          * all the moves. Otherwise we get analysis output after each undo.
13169          */
13170         if (first.analysisSupport) {
13171           SendToProgram("exit\nforce\n", &first);
13172           first.analyzing = FALSE;
13173         }
13174     }
13175
13176     if (gameMode == IcsExamining && !pausing) {
13177         SendToICS(ics_prefix);
13178         SendToICS("backward 999999\n");
13179     } else {
13180         BackwardInner(backwardMostMove);
13181     }
13182
13183     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13184         /* we have fed all the moves, so reactivate analysis mode */
13185         SendToProgram("analyze\n", &first);
13186         first.analyzing = TRUE;
13187         /*first.maybeThinking = TRUE;*/
13188         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13189     }
13190 }
13191
13192 void
13193 ToNrEvent(int to)
13194 {
13195   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13196   if (to >= forwardMostMove) to = forwardMostMove;
13197   if (to <= backwardMostMove) to = backwardMostMove;
13198   if (to < currentMove) {
13199     BackwardInner(to);
13200   } else {
13201     ForwardInner(to);
13202   }
13203 }
13204
13205 void
13206 RevertEvent(Boolean annotate)
13207 {
13208     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13209         return;
13210     }
13211     if (gameMode != IcsExamining) {
13212         DisplayError(_("You are not examining a game"), 0);
13213         return;
13214     }
13215     if (pausing) {
13216         DisplayError(_("You can't revert while pausing"), 0);
13217         return;
13218     }
13219     SendToICS(ics_prefix);
13220     SendToICS("revert\n");
13221 }
13222
13223 void
13224 RetractMoveEvent()
13225 {
13226     switch (gameMode) {
13227       case MachinePlaysWhite:
13228       case MachinePlaysBlack:
13229         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13230             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13231             return;
13232         }
13233         if (forwardMostMove < 2) return;
13234         currentMove = forwardMostMove = forwardMostMove - 2;
13235         whiteTimeRemaining = timeRemaining[0][currentMove];
13236         blackTimeRemaining = timeRemaining[1][currentMove];
13237         DisplayBothClocks();
13238         DisplayMove(currentMove - 1);
13239         ClearHighlights();/*!! could figure this out*/
13240         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13241         SendToProgram("remove\n", &first);
13242         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13243         break;
13244
13245       case BeginningOfGame:
13246       default:
13247         break;
13248
13249       case IcsPlayingWhite:
13250       case IcsPlayingBlack:
13251         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13252             SendToICS(ics_prefix);
13253             SendToICS("takeback 2\n");
13254         } else {
13255             SendToICS(ics_prefix);
13256             SendToICS("takeback 1\n");
13257         }
13258         break;
13259     }
13260 }
13261
13262 void
13263 MoveNowEvent()
13264 {
13265     ChessProgramState *cps;
13266
13267     switch (gameMode) {
13268       case MachinePlaysWhite:
13269         if (!WhiteOnMove(forwardMostMove)) {
13270             DisplayError(_("It is your turn"), 0);
13271             return;
13272         }
13273         cps = &first;
13274         break;
13275       case MachinePlaysBlack:
13276         if (WhiteOnMove(forwardMostMove)) {
13277             DisplayError(_("It is your turn"), 0);
13278             return;
13279         }
13280         cps = &first;
13281         break;
13282       case TwoMachinesPlay:
13283         if (WhiteOnMove(forwardMostMove) ==
13284             (first.twoMachinesColor[0] == 'w')) {
13285             cps = &first;
13286         } else {
13287             cps = &second;
13288         }
13289         break;
13290       case BeginningOfGame:
13291       default:
13292         return;
13293     }
13294     SendToProgram("?\n", cps);
13295 }
13296
13297 void
13298 TruncateGameEvent()
13299 {
13300     EditGameEvent();
13301     if (gameMode != EditGame) return;
13302     TruncateGame();
13303 }
13304
13305 void
13306 TruncateGame()
13307 {
13308     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13309     if (forwardMostMove > currentMove) {
13310         if (gameInfo.resultDetails != NULL) {
13311             free(gameInfo.resultDetails);
13312             gameInfo.resultDetails = NULL;
13313             gameInfo.result = GameUnfinished;
13314         }
13315         forwardMostMove = currentMove;
13316         HistorySet(parseList, backwardMostMove, forwardMostMove,
13317                    currentMove-1);
13318     }
13319 }
13320
13321 void
13322 HintEvent()
13323 {
13324     if (appData.noChessProgram) return;
13325     switch (gameMode) {
13326       case MachinePlaysWhite:
13327         if (WhiteOnMove(forwardMostMove)) {
13328             DisplayError(_("Wait until your turn"), 0);
13329             return;
13330         }
13331         break;
13332       case BeginningOfGame:
13333       case MachinePlaysBlack:
13334         if (!WhiteOnMove(forwardMostMove)) {
13335             DisplayError(_("Wait until your turn"), 0);
13336             return;
13337         }
13338         break;
13339       default:
13340         DisplayError(_("No hint available"), 0);
13341         return;
13342     }
13343     SendToProgram("hint\n", &first);
13344     hintRequested = TRUE;
13345 }
13346
13347 void
13348 BookEvent()
13349 {
13350     if (appData.noChessProgram) return;
13351     switch (gameMode) {
13352       case MachinePlaysWhite:
13353         if (WhiteOnMove(forwardMostMove)) {
13354             DisplayError(_("Wait until your turn"), 0);
13355             return;
13356         }
13357         break;
13358       case BeginningOfGame:
13359       case MachinePlaysBlack:
13360         if (!WhiteOnMove(forwardMostMove)) {
13361             DisplayError(_("Wait until your turn"), 0);
13362             return;
13363         }
13364         break;
13365       case EditPosition:
13366         EditPositionDone(TRUE);
13367         break;
13368       case TwoMachinesPlay:
13369         return;
13370       default:
13371         break;
13372     }
13373     SendToProgram("bk\n", &first);
13374     bookOutput[0] = NULLCHAR;
13375     bookRequested = TRUE;
13376 }
13377
13378 void
13379 AboutGameEvent()
13380 {
13381     char *tags = PGNTags(&gameInfo);
13382     TagsPopUp(tags, CmailMsg());
13383     free(tags);
13384 }
13385
13386 /* end button procedures */
13387
13388 void
13389 PrintPosition(fp, move)
13390      FILE *fp;
13391      int move;
13392 {
13393     int i, j;
13394
13395     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13396         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13397             char c = PieceToChar(boards[move][i][j]);
13398             fputc(c == 'x' ? '.' : c, fp);
13399             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13400         }
13401     }
13402     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13403       fprintf(fp, "white to play\n");
13404     else
13405       fprintf(fp, "black to play\n");
13406 }
13407
13408 void
13409 PrintOpponents(fp)
13410      FILE *fp;
13411 {
13412     if (gameInfo.white != NULL) {
13413         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13414     } else {
13415         fprintf(fp, "\n");
13416     }
13417 }
13418
13419 /* Find last component of program's own name, using some heuristics */
13420 void
13421 TidyProgramName(prog, host, buf)
13422      char *prog, *host, buf[MSG_SIZ];
13423 {
13424     char *p, *q;
13425     int local = (strcmp(host, "localhost") == 0);
13426     while (!local && (p = strchr(prog, ';')) != NULL) {
13427         p++;
13428         while (*p == ' ') p++;
13429         prog = p;
13430     }
13431     if (*prog == '"' || *prog == '\'') {
13432         q = strchr(prog + 1, *prog);
13433     } else {
13434         q = strchr(prog, ' ');
13435     }
13436     if (q == NULL) q = prog + strlen(prog);
13437     p = q;
13438     while (p >= prog && *p != '/' && *p != '\\') p--;
13439     p++;
13440     if(p == prog && *p == '"') p++;
13441     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13442     memcpy(buf, p, q - p);
13443     buf[q - p] = NULLCHAR;
13444     if (!local) {
13445         strcat(buf, "@");
13446         strcat(buf, host);
13447     }
13448 }
13449
13450 char *
13451 TimeControlTagValue()
13452 {
13453     char buf[MSG_SIZ];
13454     if (!appData.clockMode) {
13455       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13456     } else if (movesPerSession > 0) {
13457       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13458     } else if (timeIncrement == 0) {
13459       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13460     } else {
13461       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13462     }
13463     return StrSave(buf);
13464 }
13465
13466 void
13467 SetGameInfo()
13468 {
13469     /* This routine is used only for certain modes */
13470     VariantClass v = gameInfo.variant;
13471     ChessMove r = GameUnfinished;
13472     char *p = NULL;
13473
13474     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13475         r = gameInfo.result;
13476         p = gameInfo.resultDetails;
13477         gameInfo.resultDetails = NULL;
13478     }
13479     ClearGameInfo(&gameInfo);
13480     gameInfo.variant = v;
13481
13482     switch (gameMode) {
13483       case MachinePlaysWhite:
13484         gameInfo.event = StrSave( appData.pgnEventHeader );
13485         gameInfo.site = StrSave(HostName());
13486         gameInfo.date = PGNDate();
13487         gameInfo.round = StrSave("-");
13488         gameInfo.white = StrSave(first.tidy);
13489         gameInfo.black = StrSave(UserName());
13490         gameInfo.timeControl = TimeControlTagValue();
13491         break;
13492
13493       case MachinePlaysBlack:
13494         gameInfo.event = StrSave( appData.pgnEventHeader );
13495         gameInfo.site = StrSave(HostName());
13496         gameInfo.date = PGNDate();
13497         gameInfo.round = StrSave("-");
13498         gameInfo.white = StrSave(UserName());
13499         gameInfo.black = StrSave(first.tidy);
13500         gameInfo.timeControl = TimeControlTagValue();
13501         break;
13502
13503       case TwoMachinesPlay:
13504         gameInfo.event = StrSave( appData.pgnEventHeader );
13505         gameInfo.site = StrSave(HostName());
13506         gameInfo.date = PGNDate();
13507         if (matchGame > 0) {
13508             char buf[MSG_SIZ];
13509             snprintf(buf, MSG_SIZ, "%d", matchGame);
13510             gameInfo.round = StrSave(buf);
13511         } else {
13512             gameInfo.round = StrSave("-");
13513         }
13514         if (first.twoMachinesColor[0] == 'w') {
13515             gameInfo.white = StrSave(first.tidy);
13516             gameInfo.black = StrSave(second.tidy);
13517         } else {
13518             gameInfo.white = StrSave(second.tidy);
13519             gameInfo.black = StrSave(first.tidy);
13520         }
13521         gameInfo.timeControl = TimeControlTagValue();
13522         break;
13523
13524       case EditGame:
13525         gameInfo.event = StrSave("Edited game");
13526         gameInfo.site = StrSave(HostName());
13527         gameInfo.date = PGNDate();
13528         gameInfo.round = StrSave("-");
13529         gameInfo.white = StrSave("-");
13530         gameInfo.black = StrSave("-");
13531         gameInfo.result = r;
13532         gameInfo.resultDetails = p;
13533         break;
13534
13535       case EditPosition:
13536         gameInfo.event = StrSave("Edited position");
13537         gameInfo.site = StrSave(HostName());
13538         gameInfo.date = PGNDate();
13539         gameInfo.round = StrSave("-");
13540         gameInfo.white = StrSave("-");
13541         gameInfo.black = StrSave("-");
13542         break;
13543
13544       case IcsPlayingWhite:
13545       case IcsPlayingBlack:
13546       case IcsObserving:
13547       case IcsExamining:
13548         break;
13549
13550       case PlayFromGameFile:
13551         gameInfo.event = StrSave("Game from non-PGN file");
13552         gameInfo.site = StrSave(HostName());
13553         gameInfo.date = PGNDate();
13554         gameInfo.round = StrSave("-");
13555         gameInfo.white = StrSave("?");
13556         gameInfo.black = StrSave("?");
13557         break;
13558
13559       default:
13560         break;
13561     }
13562 }
13563
13564 void
13565 ReplaceComment(index, text)
13566      int index;
13567      char *text;
13568 {
13569     int len;
13570     char *p;
13571     float score;
13572
13573     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13574        pvInfoList[index-1].depth == len &&
13575        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13576        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13577     while (*text == '\n') text++;
13578     len = strlen(text);
13579     while (len > 0 && text[len - 1] == '\n') len--;
13580
13581     if (commentList[index] != NULL)
13582       free(commentList[index]);
13583
13584     if (len == 0) {
13585         commentList[index] = NULL;
13586         return;
13587     }
13588   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13589       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13590       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13591     commentList[index] = (char *) malloc(len + 2);
13592     strncpy(commentList[index], text, len);
13593     commentList[index][len] = '\n';
13594     commentList[index][len + 1] = NULLCHAR;
13595   } else {
13596     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13597     char *p;
13598     commentList[index] = (char *) malloc(len + 7);
13599     safeStrCpy(commentList[index], "{\n", 3);
13600     safeStrCpy(commentList[index]+2, text, len+1);
13601     commentList[index][len+2] = NULLCHAR;
13602     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13603     strcat(commentList[index], "\n}\n");
13604   }
13605 }
13606
13607 void
13608 CrushCRs(text)
13609      char *text;
13610 {
13611   char *p = text;
13612   char *q = text;
13613   char ch;
13614
13615   do {
13616     ch = *p++;
13617     if (ch == '\r') continue;
13618     *q++ = ch;
13619   } while (ch != '\0');
13620 }
13621
13622 void
13623 AppendComment(index, text, addBraces)
13624      int index;
13625      char *text;
13626      Boolean addBraces; // [HGM] braces: tells if we should add {}
13627 {
13628     int oldlen, len;
13629     char *old;
13630
13631 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13632     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13633
13634     CrushCRs(text);
13635     while (*text == '\n') text++;
13636     len = strlen(text);
13637     while (len > 0 && text[len - 1] == '\n') len--;
13638
13639     if (len == 0) return;
13640
13641     if (commentList[index] != NULL) {
13642         old = commentList[index];
13643         oldlen = strlen(old);
13644         while(commentList[index][oldlen-1] ==  '\n')
13645           commentList[index][--oldlen] = NULLCHAR;
13646         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13647         safeStrCpy(commentList[index], old, oldlen + len + 6);
13648         free(old);
13649         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13650         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13651           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13652           while (*text == '\n') { text++; len--; }
13653           commentList[index][--oldlen] = NULLCHAR;
13654       }
13655         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13656         else          strcat(commentList[index], "\n");
13657         strcat(commentList[index], text);
13658         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13659         else          strcat(commentList[index], "\n");
13660     } else {
13661         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13662         if(addBraces)
13663           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13664         else commentList[index][0] = NULLCHAR;
13665         strcat(commentList[index], text);
13666         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13667         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13668     }
13669 }
13670
13671 static char * FindStr( char * text, char * sub_text )
13672 {
13673     char * result = strstr( text, sub_text );
13674
13675     if( result != NULL ) {
13676         result += strlen( sub_text );
13677     }
13678
13679     return result;
13680 }
13681
13682 /* [AS] Try to extract PV info from PGN comment */
13683 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13684 char *GetInfoFromComment( int index, char * text )
13685 {
13686     char * sep = text, *p;
13687
13688     if( text != NULL && index > 0 ) {
13689         int score = 0;
13690         int depth = 0;
13691         int time = -1, sec = 0, deci;
13692         char * s_eval = FindStr( text, "[%eval " );
13693         char * s_emt = FindStr( text, "[%emt " );
13694
13695         if( s_eval != NULL || s_emt != NULL ) {
13696             /* New style */
13697             char delim;
13698
13699             if( s_eval != NULL ) {
13700                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13701                     return text;
13702                 }
13703
13704                 if( delim != ']' ) {
13705                     return text;
13706                 }
13707             }
13708
13709             if( s_emt != NULL ) {
13710             }
13711                 return text;
13712         }
13713         else {
13714             /* We expect something like: [+|-]nnn.nn/dd */
13715             int score_lo = 0;
13716
13717             if(*text != '{') return text; // [HGM] braces: must be normal comment
13718
13719             sep = strchr( text, '/' );
13720             if( sep == NULL || sep < (text+4) ) {
13721                 return text;
13722             }
13723
13724             p = text;
13725             if(p[1] == '(') { // comment starts with PV
13726                p = strchr(p, ')'); // locate end of PV
13727                if(p == NULL || sep < p+5) return text;
13728                // at this point we have something like "{(.*) +0.23/6 ..."
13729                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13730                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13731                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13732             }
13733             time = -1; sec = -1; deci = -1;
13734             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13735                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13736                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13737                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13738                 return text;
13739             }
13740
13741             if( score_lo < 0 || score_lo >= 100 ) {
13742                 return text;
13743             }
13744
13745             if(sec >= 0) time = 600*time + 10*sec; else
13746             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13747
13748             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13749
13750             /* [HGM] PV time: now locate end of PV info */
13751             while( *++sep >= '0' && *sep <= '9'); // strip depth
13752             if(time >= 0)
13753             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13754             if(sec >= 0)
13755             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13756             if(deci >= 0)
13757             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13758             while(*sep == ' ') sep++;
13759         }
13760
13761         if( depth <= 0 ) {
13762             return text;
13763         }
13764
13765         if( time < 0 ) {
13766             time = -1;
13767         }
13768
13769         pvInfoList[index-1].depth = depth;
13770         pvInfoList[index-1].score = score;
13771         pvInfoList[index-1].time  = 10*time; // centi-sec
13772         if(*sep == '}') *sep = 0; else *--sep = '{';
13773         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13774     }
13775     return sep;
13776 }
13777
13778 void
13779 SendToProgram(message, cps)
13780      char *message;
13781      ChessProgramState *cps;
13782 {
13783     int count, outCount, error;
13784     char buf[MSG_SIZ];
13785
13786     if (cps->pr == NULL) return;
13787     Attention(cps);
13788
13789     if (appData.debugMode) {
13790         TimeMark now;
13791         GetTimeMark(&now);
13792         fprintf(debugFP, "%ld >%-6s: %s",
13793                 SubtractTimeMarks(&now, &programStartTime),
13794                 cps->which, message);
13795     }
13796
13797     count = strlen(message);
13798     outCount = OutputToProcess(cps->pr, message, count, &error);
13799     if (outCount < count && !exiting
13800                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13801       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13802       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13803         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13804             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13805                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13806                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13807             } else {
13808                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13809             }
13810             gameInfo.resultDetails = StrSave(buf);
13811         }
13812         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13813     }
13814 }
13815
13816 void
13817 ReceiveFromProgram(isr, closure, message, count, error)
13818      InputSourceRef isr;
13819      VOIDSTAR closure;
13820      char *message;
13821      int count;
13822      int error;
13823 {
13824     char *end_str;
13825     char buf[MSG_SIZ];
13826     ChessProgramState *cps = (ChessProgramState *)closure;
13827
13828     if (isr != cps->isr) return; /* Killed intentionally */
13829     if (count <= 0) {
13830         if (count == 0) {
13831             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13832                     _(cps->which), cps->program);
13833         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13834                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13835                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13836                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13837                 } else {
13838                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13839                 }
13840                 gameInfo.resultDetails = StrSave(buf);
13841             }
13842             RemoveInputSource(cps->isr);
13843             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13844         } else {
13845             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13846                     _(cps->which), cps->program);
13847             RemoveInputSource(cps->isr);
13848
13849             /* [AS] Program is misbehaving badly... kill it */
13850             if( count == -2 ) {
13851                 DestroyChildProcess( cps->pr, 9 );
13852                 cps->pr = NoProc;
13853             }
13854
13855             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13856         }
13857         return;
13858     }
13859
13860     if ((end_str = strchr(message, '\r')) != NULL)
13861       *end_str = NULLCHAR;
13862     if ((end_str = strchr(message, '\n')) != NULL)
13863       *end_str = NULLCHAR;
13864
13865     if (appData.debugMode) {
13866         TimeMark now; int print = 1;
13867         char *quote = ""; char c; int i;
13868
13869         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13870                 char start = message[0];
13871                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13872                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13873                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13874                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13875                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13876                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13877                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13878                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13879                    sscanf(message, "hint: %c", &c)!=1 && 
13880                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13881                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13882                     print = (appData.engineComments >= 2);
13883                 }
13884                 message[0] = start; // restore original message
13885         }
13886         if(print) {
13887                 GetTimeMark(&now);
13888                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13889                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13890                         quote,
13891                         message);
13892         }
13893     }
13894
13895     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13896     if (appData.icsEngineAnalyze) {
13897         if (strstr(message, "whisper") != NULL ||
13898              strstr(message, "kibitz") != NULL ||
13899             strstr(message, "tellics") != NULL) return;
13900     }
13901
13902     HandleMachineMove(message, cps);
13903 }
13904
13905
13906 void
13907 SendTimeControl(cps, mps, tc, inc, sd, st)
13908      ChessProgramState *cps;
13909      int mps, inc, sd, st;
13910      long tc;
13911 {
13912     char buf[MSG_SIZ];
13913     int seconds;
13914
13915     if( timeControl_2 > 0 ) {
13916         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13917             tc = timeControl_2;
13918         }
13919     }
13920     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13921     inc /= cps->timeOdds;
13922     st  /= cps->timeOdds;
13923
13924     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13925
13926     if (st > 0) {
13927       /* Set exact time per move, normally using st command */
13928       if (cps->stKludge) {
13929         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13930         seconds = st % 60;
13931         if (seconds == 0) {
13932           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13933         } else {
13934           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13935         }
13936       } else {
13937         snprintf(buf, MSG_SIZ, "st %d\n", st);
13938       }
13939     } else {
13940       /* Set conventional or incremental time control, using level command */
13941       if (seconds == 0) {
13942         /* Note old gnuchess bug -- minutes:seconds used to not work.
13943            Fixed in later versions, but still avoid :seconds
13944            when seconds is 0. */
13945         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13946       } else {
13947         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13948                  seconds, inc/1000.);
13949       }
13950     }
13951     SendToProgram(buf, cps);
13952
13953     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13954     /* Orthogonally, limit search to given depth */
13955     if (sd > 0) {
13956       if (cps->sdKludge) {
13957         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13958       } else {
13959         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13960       }
13961       SendToProgram(buf, cps);
13962     }
13963
13964     if(cps->nps >= 0) { /* [HGM] nps */
13965         if(cps->supportsNPS == FALSE)
13966           cps->nps = -1; // don't use if engine explicitly says not supported!
13967         else {
13968           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13969           SendToProgram(buf, cps);
13970         }
13971     }
13972 }
13973
13974 ChessProgramState *WhitePlayer()
13975 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13976 {
13977     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13978        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13979         return &second;
13980     return &first;
13981 }
13982
13983 void
13984 SendTimeRemaining(cps, machineWhite)
13985      ChessProgramState *cps;
13986      int /*boolean*/ machineWhite;
13987 {
13988     char message[MSG_SIZ];
13989     long time, otime;
13990
13991     /* Note: this routine must be called when the clocks are stopped
13992        or when they have *just* been set or switched; otherwise
13993        it will be off by the time since the current tick started.
13994     */
13995     if (machineWhite) {
13996         time = whiteTimeRemaining / 10;
13997         otime = blackTimeRemaining / 10;
13998     } else {
13999         time = blackTimeRemaining / 10;
14000         otime = whiteTimeRemaining / 10;
14001     }
14002     /* [HGM] translate opponent's time by time-odds factor */
14003     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14004     if (appData.debugMode) {
14005         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14006     }
14007
14008     if (time <= 0) time = 1;
14009     if (otime <= 0) otime = 1;
14010
14011     snprintf(message, MSG_SIZ, "time %ld\n", time);
14012     SendToProgram(message, cps);
14013
14014     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14015     SendToProgram(message, cps);
14016 }
14017
14018 int
14019 BoolFeature(p, name, loc, cps)
14020      char **p;
14021      char *name;
14022      int *loc;
14023      ChessProgramState *cps;
14024 {
14025   char buf[MSG_SIZ];
14026   int len = strlen(name);
14027   int val;
14028
14029   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14030     (*p) += len + 1;
14031     sscanf(*p, "%d", &val);
14032     *loc = (val != 0);
14033     while (**p && **p != ' ')
14034       (*p)++;
14035     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14036     SendToProgram(buf, cps);
14037     return TRUE;
14038   }
14039   return FALSE;
14040 }
14041
14042 int
14043 IntFeature(p, name, loc, cps)
14044      char **p;
14045      char *name;
14046      int *loc;
14047      ChessProgramState *cps;
14048 {
14049   char buf[MSG_SIZ];
14050   int len = strlen(name);
14051   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14052     (*p) += len + 1;
14053     sscanf(*p, "%d", loc);
14054     while (**p && **p != ' ') (*p)++;
14055     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14056     SendToProgram(buf, cps);
14057     return TRUE;
14058   }
14059   return FALSE;
14060 }
14061
14062 int
14063 StringFeature(p, name, loc, cps)
14064      char **p;
14065      char *name;
14066      char loc[];
14067      ChessProgramState *cps;
14068 {
14069   char buf[MSG_SIZ];
14070   int len = strlen(name);
14071   if (strncmp((*p), name, len) == 0
14072       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14073     (*p) += len + 2;
14074     sscanf(*p, "%[^\"]", loc);
14075     while (**p && **p != '\"') (*p)++;
14076     if (**p == '\"') (*p)++;
14077     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14078     SendToProgram(buf, cps);
14079     return TRUE;
14080   }
14081   return FALSE;
14082 }
14083
14084 int
14085 ParseOption(Option *opt, ChessProgramState *cps)
14086 // [HGM] options: process the string that defines an engine option, and determine
14087 // name, type, default value, and allowed value range
14088 {
14089         char *p, *q, buf[MSG_SIZ];
14090         int n, min = (-1)<<31, max = 1<<31, def;
14091
14092         if(p = strstr(opt->name, " -spin ")) {
14093             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14094             if(max < min) max = min; // enforce consistency
14095             if(def < min) def = min;
14096             if(def > max) def = max;
14097             opt->value = def;
14098             opt->min = min;
14099             opt->max = max;
14100             opt->type = Spin;
14101         } else if((p = strstr(opt->name, " -slider "))) {
14102             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14103             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14104             if(max < min) max = min; // enforce consistency
14105             if(def < min) def = min;
14106             if(def > max) def = max;
14107             opt->value = def;
14108             opt->min = min;
14109             opt->max = max;
14110             opt->type = Spin; // Slider;
14111         } else if((p = strstr(opt->name, " -string "))) {
14112             opt->textValue = p+9;
14113             opt->type = TextBox;
14114         } else if((p = strstr(opt->name, " -file "))) {
14115             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14116             opt->textValue = p+7;
14117             opt->type = FileName; // FileName;
14118         } else if((p = strstr(opt->name, " -path "))) {
14119             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14120             opt->textValue = p+7;
14121             opt->type = PathName; // PathName;
14122         } else if(p = strstr(opt->name, " -check ")) {
14123             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14124             opt->value = (def != 0);
14125             opt->type = CheckBox;
14126         } else if(p = strstr(opt->name, " -combo ")) {
14127             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14128             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14129             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14130             opt->value = n = 0;
14131             while(q = StrStr(q, " /// ")) {
14132                 n++; *q = 0;    // count choices, and null-terminate each of them
14133                 q += 5;
14134                 if(*q == '*') { // remember default, which is marked with * prefix
14135                     q++;
14136                     opt->value = n;
14137                 }
14138                 cps->comboList[cps->comboCnt++] = q;
14139             }
14140             cps->comboList[cps->comboCnt++] = NULL;
14141             opt->max = n + 1;
14142             opt->type = ComboBox;
14143         } else if(p = strstr(opt->name, " -button")) {
14144             opt->type = Button;
14145         } else if(p = strstr(opt->name, " -save")) {
14146             opt->type = SaveButton;
14147         } else return FALSE;
14148         *p = 0; // terminate option name
14149         // now look if the command-line options define a setting for this engine option.
14150         if(cps->optionSettings && cps->optionSettings[0])
14151             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14152         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14153           snprintf(buf, MSG_SIZ, "option %s", p);
14154                 if(p = strstr(buf, ",")) *p = 0;
14155                 if(q = strchr(buf, '=')) switch(opt->type) {
14156                     case ComboBox:
14157                         for(n=0; n<opt->max; n++)
14158                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14159                         break;
14160                     case TextBox:
14161                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14162                         break;
14163                     case Spin:
14164                     case CheckBox:
14165                         opt->value = atoi(q+1);
14166                     default:
14167                         break;
14168                 }
14169                 strcat(buf, "\n");
14170                 SendToProgram(buf, cps);
14171         }
14172         return TRUE;
14173 }
14174
14175 void
14176 FeatureDone(cps, val)
14177      ChessProgramState* cps;
14178      int val;
14179 {
14180   DelayedEventCallback cb = GetDelayedEvent();
14181   if ((cb == InitBackEnd3 && cps == &first) ||
14182       (cb == SettingsMenuIfReady && cps == &second) ||
14183       (cb == LoadEngine) ||
14184       (cb == TwoMachinesEventIfReady && cps == &second)) {
14185     CancelDelayedEvent();
14186     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14187   }
14188   cps->initDone = val;
14189 }
14190
14191 /* Parse feature command from engine */
14192 void
14193 ParseFeatures(args, cps)
14194      char* args;
14195      ChessProgramState *cps;
14196 {
14197   char *p = args;
14198   char *q;
14199   int val;
14200   char buf[MSG_SIZ];
14201
14202   for (;;) {
14203     while (*p == ' ') p++;
14204     if (*p == NULLCHAR) return;
14205
14206     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14207     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14208     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14209     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14210     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14211     if (BoolFeature(&p, "reuse", &val, cps)) {
14212       /* Engine can disable reuse, but can't enable it if user said no */
14213       if (!val) cps->reuse = FALSE;
14214       continue;
14215     }
14216     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14217     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14218       if (gameMode == TwoMachinesPlay) {
14219         DisplayTwoMachinesTitle();
14220       } else {
14221         DisplayTitle("");
14222       }
14223       continue;
14224     }
14225     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14226     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14227     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14228     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14229     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14230     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14231     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14232     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14233     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14234     if (IntFeature(&p, "done", &val, cps)) {
14235       FeatureDone(cps, val);
14236       continue;
14237     }
14238     /* Added by Tord: */
14239     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14240     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14241     /* End of additions by Tord */
14242
14243     /* [HGM] added features: */
14244     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14245     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14246     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14247     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14248     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14249     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14250     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14251         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14252           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14253             SendToProgram(buf, cps);
14254             continue;
14255         }
14256         if(cps->nrOptions >= MAX_OPTIONS) {
14257             cps->nrOptions--;
14258             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14259             DisplayError(buf, 0);
14260         }
14261         continue;
14262     }
14263     /* End of additions by HGM */
14264
14265     /* unknown feature: complain and skip */
14266     q = p;
14267     while (*q && *q != '=') q++;
14268     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14269     SendToProgram(buf, cps);
14270     p = q;
14271     if (*p == '=') {
14272       p++;
14273       if (*p == '\"') {
14274         p++;
14275         while (*p && *p != '\"') p++;
14276         if (*p == '\"') p++;
14277       } else {
14278         while (*p && *p != ' ') p++;
14279       }
14280     }
14281   }
14282
14283 }
14284
14285 void
14286 PeriodicUpdatesEvent(newState)
14287      int newState;
14288 {
14289     if (newState == appData.periodicUpdates)
14290       return;
14291
14292     appData.periodicUpdates=newState;
14293
14294     /* Display type changes, so update it now */
14295 //    DisplayAnalysis();
14296
14297     /* Get the ball rolling again... */
14298     if (newState) {
14299         AnalysisPeriodicEvent(1);
14300         StartAnalysisClock();
14301     }
14302 }
14303
14304 void
14305 PonderNextMoveEvent(newState)
14306      int newState;
14307 {
14308     if (newState == appData.ponderNextMove) return;
14309     if (gameMode == EditPosition) EditPositionDone(TRUE);
14310     if (newState) {
14311         SendToProgram("hard\n", &first);
14312         if (gameMode == TwoMachinesPlay) {
14313             SendToProgram("hard\n", &second);
14314         }
14315     } else {
14316         SendToProgram("easy\n", &first);
14317         thinkOutput[0] = NULLCHAR;
14318         if (gameMode == TwoMachinesPlay) {
14319             SendToProgram("easy\n", &second);
14320         }
14321     }
14322     appData.ponderNextMove = newState;
14323 }
14324
14325 void
14326 NewSettingEvent(option, feature, command, value)
14327      char *command;
14328      int option, value, *feature;
14329 {
14330     char buf[MSG_SIZ];
14331
14332     if (gameMode == EditPosition) EditPositionDone(TRUE);
14333     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14334     if(feature == NULL || *feature) SendToProgram(buf, &first);
14335     if (gameMode == TwoMachinesPlay) {
14336         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14337     }
14338 }
14339
14340 void
14341 ShowThinkingEvent()
14342 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14343 {
14344     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14345     int newState = appData.showThinking
14346         // [HGM] thinking: other features now need thinking output as well
14347         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14348
14349     if (oldState == newState) return;
14350     oldState = newState;
14351     if (gameMode == EditPosition) EditPositionDone(TRUE);
14352     if (oldState) {
14353         SendToProgram("post\n", &first);
14354         if (gameMode == TwoMachinesPlay) {
14355             SendToProgram("post\n", &second);
14356         }
14357     } else {
14358         SendToProgram("nopost\n", &first);
14359         thinkOutput[0] = NULLCHAR;
14360         if (gameMode == TwoMachinesPlay) {
14361             SendToProgram("nopost\n", &second);
14362         }
14363     }
14364 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14365 }
14366
14367 void
14368 AskQuestionEvent(title, question, replyPrefix, which)
14369      char *title; char *question; char *replyPrefix; char *which;
14370 {
14371   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14372   if (pr == NoProc) return;
14373   AskQuestion(title, question, replyPrefix, pr);
14374 }
14375
14376 void
14377 TypeInEvent(char firstChar)
14378 {
14379     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14380         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14381         gameMode == AnalyzeMode || gameMode == EditGame || \r
14382         gameMode == EditPosition || gameMode == IcsExamining ||\r
14383         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14384         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14385                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14386                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14387         gameMode == Training) PopUpMoveDialog(firstChar);
14388 }
14389
14390 void
14391 TypeInDoneEvent(char *move)
14392 {
14393         Board board;
14394         int n, fromX, fromY, toX, toY;
14395         char promoChar;
14396         ChessMove moveType;\r
14397
14398         // [HGM] FENedit\r
14399         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14400                 EditPositionPasteFEN(move);\r
14401                 return;\r
14402         }\r
14403         // [HGM] movenum: allow move number to be typed in any mode\r
14404         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14405           ToNrEvent(2*n-1);\r
14406           return;\r
14407         }\r
14408
14409       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14410         gameMode != Training) {\r
14411         DisplayMoveError(_("Displayed move is not current"));\r
14412       } else {\r
14413         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14414           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14415         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14416         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14417           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14418           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14419         } else {\r
14420           DisplayMoveError(_("Could not parse move"));\r
14421         }
14422       }\r
14423 }\r
14424
14425 void
14426 DisplayMove(moveNumber)
14427      int moveNumber;
14428 {
14429     char message[MSG_SIZ];
14430     char res[MSG_SIZ];
14431     char cpThinkOutput[MSG_SIZ];
14432
14433     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14434
14435     if (moveNumber == forwardMostMove - 1 ||
14436         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14437
14438         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14439
14440         if (strchr(cpThinkOutput, '\n')) {
14441             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14442         }
14443     } else {
14444         *cpThinkOutput = NULLCHAR;
14445     }
14446
14447     /* [AS] Hide thinking from human user */
14448     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14449         *cpThinkOutput = NULLCHAR;
14450         if( thinkOutput[0] != NULLCHAR ) {
14451             int i;
14452
14453             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14454                 cpThinkOutput[i] = '.';
14455             }
14456             cpThinkOutput[i] = NULLCHAR;
14457             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14458         }
14459     }
14460
14461     if (moveNumber == forwardMostMove - 1 &&
14462         gameInfo.resultDetails != NULL) {
14463         if (gameInfo.resultDetails[0] == NULLCHAR) {
14464           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14465         } else {
14466           snprintf(res, MSG_SIZ, " {%s} %s",
14467                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14468         }
14469     } else {
14470         res[0] = NULLCHAR;
14471     }
14472
14473     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14474         DisplayMessage(res, cpThinkOutput);
14475     } else {
14476       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14477                 WhiteOnMove(moveNumber) ? " " : ".. ",
14478                 parseList[moveNumber], res);
14479         DisplayMessage(message, cpThinkOutput);
14480     }
14481 }
14482
14483 void
14484 DisplayComment(moveNumber, text)
14485      int moveNumber;
14486      char *text;
14487 {
14488     char title[MSG_SIZ];
14489     char buf[8000]; // comment can be long!
14490     int score, depth;
14491
14492     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14493       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14494     } else {
14495       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14496               WhiteOnMove(moveNumber) ? " " : ".. ",
14497               parseList[moveNumber]);
14498     }
14499     // [HGM] PV info: display PV info together with (or as) comment
14500     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14501       if(text == NULL) text = "";
14502       score = pvInfoList[moveNumber].score;
14503       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14504               depth, (pvInfoList[moveNumber].time+50)/100, text);
14505       text = buf;
14506     }
14507     if (text != NULL && (appData.autoDisplayComment || commentUp))
14508         CommentPopUp(title, text);
14509 }
14510
14511 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14512  * might be busy thinking or pondering.  It can be omitted if your
14513  * gnuchess is configured to stop thinking immediately on any user
14514  * input.  However, that gnuchess feature depends on the FIONREAD
14515  * ioctl, which does not work properly on some flavors of Unix.
14516  */
14517 void
14518 Attention(cps)
14519      ChessProgramState *cps;
14520 {
14521 #if ATTENTION
14522     if (!cps->useSigint) return;
14523     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14524     switch (gameMode) {
14525       case MachinePlaysWhite:
14526       case MachinePlaysBlack:
14527       case TwoMachinesPlay:
14528       case IcsPlayingWhite:
14529       case IcsPlayingBlack:
14530       case AnalyzeMode:
14531       case AnalyzeFile:
14532         /* Skip if we know it isn't thinking */
14533         if (!cps->maybeThinking) return;
14534         if (appData.debugMode)
14535           fprintf(debugFP, "Interrupting %s\n", cps->which);
14536         InterruptChildProcess(cps->pr);
14537         cps->maybeThinking = FALSE;
14538         break;
14539       default:
14540         break;
14541     }
14542 #endif /*ATTENTION*/
14543 }
14544
14545 int
14546 CheckFlags()
14547 {
14548     if (whiteTimeRemaining <= 0) {
14549         if (!whiteFlag) {
14550             whiteFlag = TRUE;
14551             if (appData.icsActive) {
14552                 if (appData.autoCallFlag &&
14553                     gameMode == IcsPlayingBlack && !blackFlag) {
14554                   SendToICS(ics_prefix);
14555                   SendToICS("flag\n");
14556                 }
14557             } else {
14558                 if (blackFlag) {
14559                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14560                 } else {
14561                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14562                     if (appData.autoCallFlag) {
14563                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14564                         return TRUE;
14565                     }
14566                 }
14567             }
14568         }
14569     }
14570     if (blackTimeRemaining <= 0) {
14571         if (!blackFlag) {
14572             blackFlag = TRUE;
14573             if (appData.icsActive) {
14574                 if (appData.autoCallFlag &&
14575                     gameMode == IcsPlayingWhite && !whiteFlag) {
14576                   SendToICS(ics_prefix);
14577                   SendToICS("flag\n");
14578                 }
14579             } else {
14580                 if (whiteFlag) {
14581                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14582                 } else {
14583                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14584                     if (appData.autoCallFlag) {
14585                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14586                         return TRUE;
14587                     }
14588                 }
14589             }
14590         }
14591     }
14592     return FALSE;
14593 }
14594
14595 void
14596 CheckTimeControl()
14597 {
14598     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14599         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14600
14601     /*
14602      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14603      */
14604     if ( !WhiteOnMove(forwardMostMove) ) {
14605         /* White made time control */
14606         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14607         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14608         /* [HGM] time odds: correct new time quota for time odds! */
14609                                             / WhitePlayer()->timeOdds;
14610         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14611     } else {
14612         lastBlack -= blackTimeRemaining;
14613         /* Black made time control */
14614         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14615                                             / WhitePlayer()->other->timeOdds;
14616         lastWhite = whiteTimeRemaining;
14617     }
14618 }
14619
14620 void
14621 DisplayBothClocks()
14622 {
14623     int wom = gameMode == EditPosition ?
14624       !blackPlaysFirst : WhiteOnMove(currentMove);
14625     DisplayWhiteClock(whiteTimeRemaining, wom);
14626     DisplayBlackClock(blackTimeRemaining, !wom);
14627 }
14628
14629
14630 /* Timekeeping seems to be a portability nightmare.  I think everyone
14631    has ftime(), but I'm really not sure, so I'm including some ifdefs
14632    to use other calls if you don't.  Clocks will be less accurate if
14633    you have neither ftime nor gettimeofday.
14634 */
14635
14636 /* VS 2008 requires the #include outside of the function */
14637 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14638 #include <sys/timeb.h>
14639 #endif
14640
14641 /* Get the current time as a TimeMark */
14642 void
14643 GetTimeMark(tm)
14644      TimeMark *tm;
14645 {
14646 #if HAVE_GETTIMEOFDAY
14647
14648     struct timeval timeVal;
14649     struct timezone timeZone;
14650
14651     gettimeofday(&timeVal, &timeZone);
14652     tm->sec = (long) timeVal.tv_sec;
14653     tm->ms = (int) (timeVal.tv_usec / 1000L);
14654
14655 #else /*!HAVE_GETTIMEOFDAY*/
14656 #if HAVE_FTIME
14657
14658 // include <sys/timeb.h> / moved to just above start of function
14659     struct timeb timeB;
14660
14661     ftime(&timeB);
14662     tm->sec = (long) timeB.time;
14663     tm->ms = (int) timeB.millitm;
14664
14665 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14666     tm->sec = (long) time(NULL);
14667     tm->ms = 0;
14668 #endif
14669 #endif
14670 }
14671
14672 /* Return the difference in milliseconds between two
14673    time marks.  We assume the difference will fit in a long!
14674 */
14675 long
14676 SubtractTimeMarks(tm2, tm1)
14677      TimeMark *tm2, *tm1;
14678 {
14679     return 1000L*(tm2->sec - tm1->sec) +
14680            (long) (tm2->ms - tm1->ms);
14681 }
14682
14683
14684 /*
14685  * Code to manage the game clocks.
14686  *
14687  * In tournament play, black starts the clock and then white makes a move.
14688  * We give the human user a slight advantage if he is playing white---the
14689  * clocks don't run until he makes his first move, so it takes zero time.
14690  * Also, we don't account for network lag, so we could get out of sync
14691  * with GNU Chess's clock -- but then, referees are always right.
14692  */
14693
14694 static TimeMark tickStartTM;
14695 static long intendedTickLength;
14696
14697 long
14698 NextTickLength(timeRemaining)
14699      long timeRemaining;
14700 {
14701     long nominalTickLength, nextTickLength;
14702
14703     if (timeRemaining > 0L && timeRemaining <= 10000L)
14704       nominalTickLength = 100L;
14705     else
14706       nominalTickLength = 1000L;
14707     nextTickLength = timeRemaining % nominalTickLength;
14708     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14709
14710     return nextTickLength;
14711 }
14712
14713 /* Adjust clock one minute up or down */
14714 void
14715 AdjustClock(Boolean which, int dir)
14716 {
14717     if(which) blackTimeRemaining += 60000*dir;
14718     else      whiteTimeRemaining += 60000*dir;
14719     DisplayBothClocks();
14720 }
14721
14722 /* Stop clocks and reset to a fresh time control */
14723 void
14724 ResetClocks()
14725 {
14726     (void) StopClockTimer();
14727     if (appData.icsActive) {
14728         whiteTimeRemaining = blackTimeRemaining = 0;
14729     } else if (searchTime) {
14730         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14731         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14732     } else { /* [HGM] correct new time quote for time odds */
14733         whiteTC = blackTC = fullTimeControlString;
14734         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14735         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14736     }
14737     if (whiteFlag || blackFlag) {
14738         DisplayTitle("");
14739         whiteFlag = blackFlag = FALSE;
14740     }
14741     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14742     DisplayBothClocks();
14743 }
14744
14745 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14746
14747 /* Decrement running clock by amount of time that has passed */
14748 void
14749 DecrementClocks()
14750 {
14751     long timeRemaining;
14752     long lastTickLength, fudge;
14753     TimeMark now;
14754
14755     if (!appData.clockMode) return;
14756     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14757
14758     GetTimeMark(&now);
14759
14760     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14761
14762     /* Fudge if we woke up a little too soon */
14763     fudge = intendedTickLength - lastTickLength;
14764     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14765
14766     if (WhiteOnMove(forwardMostMove)) {
14767         if(whiteNPS >= 0) lastTickLength = 0;
14768         timeRemaining = whiteTimeRemaining -= lastTickLength;
14769         if(timeRemaining < 0 && !appData.icsActive) {
14770             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14771             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14772                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14773                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14774             }
14775         }
14776         DisplayWhiteClock(whiteTimeRemaining - fudge,
14777                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14778     } else {
14779         if(blackNPS >= 0) lastTickLength = 0;
14780         timeRemaining = blackTimeRemaining -= lastTickLength;
14781         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14782             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14783             if(suddenDeath) {
14784                 blackStartMove = forwardMostMove;
14785                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14786             }
14787         }
14788         DisplayBlackClock(blackTimeRemaining - fudge,
14789                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14790     }
14791     if (CheckFlags()) return;
14792
14793     tickStartTM = now;
14794     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14795     StartClockTimer(intendedTickLength);
14796
14797     /* if the time remaining has fallen below the alarm threshold, sound the
14798      * alarm. if the alarm has sounded and (due to a takeback or time control
14799      * with increment) the time remaining has increased to a level above the
14800      * threshold, reset the alarm so it can sound again.
14801      */
14802
14803     if (appData.icsActive && appData.icsAlarm) {
14804
14805         /* make sure we are dealing with the user's clock */
14806         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14807                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14808            )) return;
14809
14810         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14811             alarmSounded = FALSE;
14812         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14813             PlayAlarmSound();
14814             alarmSounded = TRUE;
14815         }
14816     }
14817 }
14818
14819
14820 /* A player has just moved, so stop the previously running
14821    clock and (if in clock mode) start the other one.
14822    We redisplay both clocks in case we're in ICS mode, because
14823    ICS gives us an update to both clocks after every move.
14824    Note that this routine is called *after* forwardMostMove
14825    is updated, so the last fractional tick must be subtracted
14826    from the color that is *not* on move now.
14827 */
14828 void
14829 SwitchClocks(int newMoveNr)
14830 {
14831     long lastTickLength;
14832     TimeMark now;
14833     int flagged = FALSE;
14834
14835     GetTimeMark(&now);
14836
14837     if (StopClockTimer() && appData.clockMode) {
14838         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14839         if (!WhiteOnMove(forwardMostMove)) {
14840             if(blackNPS >= 0) lastTickLength = 0;
14841             blackTimeRemaining -= lastTickLength;
14842            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14843 //         if(pvInfoList[forwardMostMove].time == -1)
14844                  pvInfoList[forwardMostMove].time =               // use GUI time
14845                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14846         } else {
14847            if(whiteNPS >= 0) lastTickLength = 0;
14848            whiteTimeRemaining -= lastTickLength;
14849            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14850 //         if(pvInfoList[forwardMostMove].time == -1)
14851                  pvInfoList[forwardMostMove].time =
14852                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14853         }
14854         flagged = CheckFlags();
14855     }
14856     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14857     CheckTimeControl();
14858
14859     if (flagged || !appData.clockMode) return;
14860
14861     switch (gameMode) {
14862       case MachinePlaysBlack:
14863       case MachinePlaysWhite:
14864       case BeginningOfGame:
14865         if (pausing) return;
14866         break;
14867
14868       case EditGame:
14869       case PlayFromGameFile:
14870       case IcsExamining:
14871         return;
14872
14873       default:
14874         break;
14875     }
14876
14877     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14878         if(WhiteOnMove(forwardMostMove))
14879              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14880         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14881     }
14882
14883     tickStartTM = now;
14884     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14885       whiteTimeRemaining : blackTimeRemaining);
14886     StartClockTimer(intendedTickLength);
14887 }
14888
14889
14890 /* Stop both clocks */
14891 void
14892 StopClocks()
14893 {
14894     long lastTickLength;
14895     TimeMark now;
14896
14897     if (!StopClockTimer()) return;
14898     if (!appData.clockMode) return;
14899
14900     GetTimeMark(&now);
14901
14902     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14903     if (WhiteOnMove(forwardMostMove)) {
14904         if(whiteNPS >= 0) lastTickLength = 0;
14905         whiteTimeRemaining -= lastTickLength;
14906         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14907     } else {
14908         if(blackNPS >= 0) lastTickLength = 0;
14909         blackTimeRemaining -= lastTickLength;
14910         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14911     }
14912     CheckFlags();
14913 }
14914
14915 /* Start clock of player on move.  Time may have been reset, so
14916    if clock is already running, stop and restart it. */
14917 void
14918 StartClocks()
14919 {
14920     (void) StopClockTimer(); /* in case it was running already */
14921     DisplayBothClocks();
14922     if (CheckFlags()) return;
14923
14924     if (!appData.clockMode) return;
14925     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14926
14927     GetTimeMark(&tickStartTM);
14928     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14929       whiteTimeRemaining : blackTimeRemaining);
14930
14931    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14932     whiteNPS = blackNPS = -1;
14933     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14934        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14935         whiteNPS = first.nps;
14936     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14937        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14938         blackNPS = first.nps;
14939     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14940         whiteNPS = second.nps;
14941     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14942         blackNPS = second.nps;
14943     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14944
14945     StartClockTimer(intendedTickLength);
14946 }
14947
14948 char *
14949 TimeString(ms)
14950      long ms;
14951 {
14952     long second, minute, hour, day;
14953     char *sign = "";
14954     static char buf[32];
14955
14956     if (ms > 0 && ms <= 9900) {
14957       /* convert milliseconds to tenths, rounding up */
14958       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14959
14960       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14961       return buf;
14962     }
14963
14964     /* convert milliseconds to seconds, rounding up */
14965     /* use floating point to avoid strangeness of integer division
14966        with negative dividends on many machines */
14967     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14968
14969     if (second < 0) {
14970         sign = "-";
14971         second = -second;
14972     }
14973
14974     day = second / (60 * 60 * 24);
14975     second = second % (60 * 60 * 24);
14976     hour = second / (60 * 60);
14977     second = second % (60 * 60);
14978     minute = second / 60;
14979     second = second % 60;
14980
14981     if (day > 0)
14982       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14983               sign, day, hour, minute, second);
14984     else if (hour > 0)
14985       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14986     else
14987       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14988
14989     return buf;
14990 }
14991
14992
14993 /*
14994  * This is necessary because some C libraries aren't ANSI C compliant yet.
14995  */
14996 char *
14997 StrStr(string, match)
14998      char *string, *match;
14999 {
15000     int i, length;
15001
15002     length = strlen(match);
15003
15004     for (i = strlen(string) - length; i >= 0; i--, string++)
15005       if (!strncmp(match, string, length))
15006         return string;
15007
15008     return NULL;
15009 }
15010
15011 char *
15012 StrCaseStr(string, match)
15013      char *string, *match;
15014 {
15015     int i, j, length;
15016
15017     length = strlen(match);
15018
15019     for (i = strlen(string) - length; i >= 0; i--, string++) {
15020         for (j = 0; j < length; j++) {
15021             if (ToLower(match[j]) != ToLower(string[j]))
15022               break;
15023         }
15024         if (j == length) return string;
15025     }
15026
15027     return NULL;
15028 }
15029
15030 #ifndef _amigados
15031 int
15032 StrCaseCmp(s1, s2)
15033      char *s1, *s2;
15034 {
15035     char c1, c2;
15036
15037     for (;;) {
15038         c1 = ToLower(*s1++);
15039         c2 = ToLower(*s2++);
15040         if (c1 > c2) return 1;
15041         if (c1 < c2) return -1;
15042         if (c1 == NULLCHAR) return 0;
15043     }
15044 }
15045
15046
15047 int
15048 ToLower(c)
15049      int c;
15050 {
15051     return isupper(c) ? tolower(c) : c;
15052 }
15053
15054
15055 int
15056 ToUpper(c)
15057      int c;
15058 {
15059     return islower(c) ? toupper(c) : c;
15060 }
15061 #endif /* !_amigados    */
15062
15063 char *
15064 StrSave(s)
15065      char *s;
15066 {
15067   char *ret;
15068
15069   if ((ret = (char *) malloc(strlen(s) + 1)))
15070     {
15071       safeStrCpy(ret, s, strlen(s)+1);
15072     }
15073   return ret;
15074 }
15075
15076 char *
15077 StrSavePtr(s, savePtr)
15078      char *s, **savePtr;
15079 {
15080     if (*savePtr) {
15081         free(*savePtr);
15082     }
15083     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15084       safeStrCpy(*savePtr, s, strlen(s)+1);
15085     }
15086     return(*savePtr);
15087 }
15088
15089 char *
15090 PGNDate()
15091 {
15092     time_t clock;
15093     struct tm *tm;
15094     char buf[MSG_SIZ];
15095
15096     clock = time((time_t *)NULL);
15097     tm = localtime(&clock);
15098     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15099             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15100     return StrSave(buf);
15101 }
15102
15103
15104 char *
15105 PositionToFEN(move, overrideCastling)
15106      int move;
15107      char *overrideCastling;
15108 {
15109     int i, j, fromX, fromY, toX, toY;
15110     int whiteToPlay;
15111     char buf[128];
15112     char *p, *q;
15113     int emptycount;
15114     ChessSquare piece;
15115
15116     whiteToPlay = (gameMode == EditPosition) ?
15117       !blackPlaysFirst : (move % 2 == 0);
15118     p = buf;
15119
15120     /* Piece placement data */
15121     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15122         emptycount = 0;
15123         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15124             if (boards[move][i][j] == EmptySquare) {
15125                 emptycount++;
15126             } else { ChessSquare piece = boards[move][i][j];
15127                 if (emptycount > 0) {
15128                     if(emptycount<10) /* [HGM] can be >= 10 */
15129                         *p++ = '0' + emptycount;
15130                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15131                     emptycount = 0;
15132                 }
15133                 if(PieceToChar(piece) == '+') {
15134                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15135                     *p++ = '+';
15136                     piece = (ChessSquare)(DEMOTED piece);
15137                 }
15138                 *p++ = PieceToChar(piece);
15139                 if(p[-1] == '~') {
15140                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15141                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15142                     *p++ = '~';
15143                 }
15144             }
15145         }
15146         if (emptycount > 0) {
15147             if(emptycount<10) /* [HGM] can be >= 10 */
15148                 *p++ = '0' + emptycount;
15149             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15150             emptycount = 0;
15151         }
15152         *p++ = '/';
15153     }
15154     *(p - 1) = ' ';
15155
15156     /* [HGM] print Crazyhouse or Shogi holdings */
15157     if( gameInfo.holdingsWidth ) {
15158         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15159         q = p;
15160         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15161             piece = boards[move][i][BOARD_WIDTH-1];
15162             if( piece != EmptySquare )
15163               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15164                   *p++ = PieceToChar(piece);
15165         }
15166         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15167             piece = boards[move][BOARD_HEIGHT-i-1][0];
15168             if( piece != EmptySquare )
15169               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15170                   *p++ = PieceToChar(piece);
15171         }
15172
15173         if( q == p ) *p++ = '-';
15174         *p++ = ']';
15175         *p++ = ' ';
15176     }
15177
15178     /* Active color */
15179     *p++ = whiteToPlay ? 'w' : 'b';
15180     *p++ = ' ';
15181
15182   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15183     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15184   } else {
15185   if(nrCastlingRights) {
15186      q = p;
15187      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15188        /* [HGM] write directly from rights */
15189            if(boards[move][CASTLING][2] != NoRights &&
15190               boards[move][CASTLING][0] != NoRights   )
15191                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15192            if(boards[move][CASTLING][2] != NoRights &&
15193               boards[move][CASTLING][1] != NoRights   )
15194                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15195            if(boards[move][CASTLING][5] != NoRights &&
15196               boards[move][CASTLING][3] != NoRights   )
15197                 *p++ = boards[move][CASTLING][3] + AAA;
15198            if(boards[move][CASTLING][5] != NoRights &&
15199               boards[move][CASTLING][4] != NoRights   )
15200                 *p++ = boards[move][CASTLING][4] + AAA;
15201      } else {
15202
15203         /* [HGM] write true castling rights */
15204         if( nrCastlingRights == 6 ) {
15205             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15206                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15207             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15208                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15209             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15210                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15211             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15212                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15213         }
15214      }
15215      if (q == p) *p++ = '-'; /* No castling rights */
15216      *p++ = ' ';
15217   }
15218
15219   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15220      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15221     /* En passant target square */
15222     if (move > backwardMostMove) {
15223         fromX = moveList[move - 1][0] - AAA;
15224         fromY = moveList[move - 1][1] - ONE;
15225         toX = moveList[move - 1][2] - AAA;
15226         toY = moveList[move - 1][3] - ONE;
15227         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15228             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15229             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15230             fromX == toX) {
15231             /* 2-square pawn move just happened */
15232             *p++ = toX + AAA;
15233             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15234         } else {
15235             *p++ = '-';
15236         }
15237     } else if(move == backwardMostMove) {
15238         // [HGM] perhaps we should always do it like this, and forget the above?
15239         if((signed char)boards[move][EP_STATUS] >= 0) {
15240             *p++ = boards[move][EP_STATUS] + AAA;
15241             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15242         } else {
15243             *p++ = '-';
15244         }
15245     } else {
15246         *p++ = '-';
15247     }
15248     *p++ = ' ';
15249   }
15250   }
15251
15252     /* [HGM] find reversible plies */
15253     {   int i = 0, j=move;
15254
15255         if (appData.debugMode) { int k;
15256             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15257             for(k=backwardMostMove; k<=forwardMostMove; k++)
15258                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15259
15260         }
15261
15262         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15263         if( j == backwardMostMove ) i += initialRulePlies;
15264         sprintf(p, "%d ", i);
15265         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15266     }
15267     /* Fullmove number */
15268     sprintf(p, "%d", (move / 2) + 1);
15269
15270     return StrSave(buf);
15271 }
15272
15273 Boolean
15274 ParseFEN(board, blackPlaysFirst, fen)
15275     Board board;
15276      int *blackPlaysFirst;
15277      char *fen;
15278 {
15279     int i, j;
15280     char *p, c;
15281     int emptycount;
15282     ChessSquare piece;
15283
15284     p = fen;
15285
15286     /* [HGM] by default clear Crazyhouse holdings, if present */
15287     if(gameInfo.holdingsWidth) {
15288        for(i=0; i<BOARD_HEIGHT; i++) {
15289            board[i][0]             = EmptySquare; /* black holdings */
15290            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15291            board[i][1]             = (ChessSquare) 0; /* black counts */
15292            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15293        }
15294     }
15295
15296     /* Piece placement data */
15297     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15298         j = 0;
15299         for (;;) {
15300             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15301                 if (*p == '/') p++;
15302                 emptycount = gameInfo.boardWidth - j;
15303                 while (emptycount--)
15304                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15305                 break;
15306 #if(BOARD_FILES >= 10)
15307             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15308                 p++; emptycount=10;
15309                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15310                 while (emptycount--)
15311                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15312 #endif
15313             } else if (isdigit(*p)) {
15314                 emptycount = *p++ - '0';
15315                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15316                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15317                 while (emptycount--)
15318                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15319             } else if (*p == '+' || isalpha(*p)) {
15320                 if (j >= gameInfo.boardWidth) return FALSE;
15321                 if(*p=='+') {
15322                     piece = CharToPiece(*++p);
15323                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15324                     piece = (ChessSquare) (PROMOTED piece ); p++;
15325                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15326                 } else piece = CharToPiece(*p++);
15327
15328                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15329                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15330                     piece = (ChessSquare) (PROMOTED piece);
15331                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15332                     p++;
15333                 }
15334                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15335             } else {
15336                 return FALSE;
15337             }
15338         }
15339     }
15340     while (*p == '/' || *p == ' ') p++;
15341
15342     /* [HGM] look for Crazyhouse holdings here */
15343     while(*p==' ') p++;
15344     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15345         if(*p == '[') p++;
15346         if(*p == '-' ) p++; /* empty holdings */ else {
15347             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15348             /* if we would allow FEN reading to set board size, we would   */
15349             /* have to add holdings and shift the board read so far here   */
15350             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15351                 p++;
15352                 if((int) piece >= (int) BlackPawn ) {
15353                     i = (int)piece - (int)BlackPawn;
15354                     i = PieceToNumber((ChessSquare)i);
15355                     if( i >= gameInfo.holdingsSize ) return FALSE;
15356                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15357                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15358                 } else {
15359                     i = (int)piece - (int)WhitePawn;
15360                     i = PieceToNumber((ChessSquare)i);
15361                     if( i >= gameInfo.holdingsSize ) return FALSE;
15362                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15363                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15364                 }
15365             }
15366         }
15367         if(*p == ']') p++;
15368     }
15369
15370     while(*p == ' ') p++;
15371
15372     /* Active color */
15373     c = *p++;
15374     if(appData.colorNickNames) {
15375       if( c == appData.colorNickNames[0] ) c = 'w'; else
15376       if( c == appData.colorNickNames[1] ) c = 'b';
15377     }
15378     switch (c) {
15379       case 'w':
15380         *blackPlaysFirst = FALSE;
15381         break;
15382       case 'b':
15383         *blackPlaysFirst = TRUE;
15384         break;
15385       default:
15386         return FALSE;
15387     }
15388
15389     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15390     /* return the extra info in global variiables             */
15391
15392     /* set defaults in case FEN is incomplete */
15393     board[EP_STATUS] = EP_UNKNOWN;
15394     for(i=0; i<nrCastlingRights; i++ ) {
15395         board[CASTLING][i] =
15396             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15397     }   /* assume possible unless obviously impossible */
15398     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15399     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15400     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15401                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15402     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15403     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15404     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15405                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15406     FENrulePlies = 0;
15407
15408     while(*p==' ') p++;
15409     if(nrCastlingRights) {
15410       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15411           /* castling indicator present, so default becomes no castlings */
15412           for(i=0; i<nrCastlingRights; i++ ) {
15413                  board[CASTLING][i] = NoRights;
15414           }
15415       }
15416       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15417              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15418              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15419              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15420         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15421
15422         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15423             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15424             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15425         }
15426         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15427             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15428         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15429                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15430         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15431                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15432         switch(c) {
15433           case'K':
15434               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15435               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15436               board[CASTLING][2] = whiteKingFile;
15437               break;
15438           case'Q':
15439               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15440               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15441               board[CASTLING][2] = whiteKingFile;
15442               break;
15443           case'k':
15444               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15445               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15446               board[CASTLING][5] = blackKingFile;
15447               break;
15448           case'q':
15449               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15450               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15451               board[CASTLING][5] = blackKingFile;
15452           case '-':
15453               break;
15454           default: /* FRC castlings */
15455               if(c >= 'a') { /* black rights */
15456                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15457                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15458                   if(i == BOARD_RGHT) break;
15459                   board[CASTLING][5] = i;
15460                   c -= AAA;
15461                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15462                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15463                   if(c > i)
15464                       board[CASTLING][3] = c;
15465                   else
15466                       board[CASTLING][4] = c;
15467               } else { /* white rights */
15468                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15469                     if(board[0][i] == WhiteKing) break;
15470                   if(i == BOARD_RGHT) break;
15471                   board[CASTLING][2] = i;
15472                   c -= AAA - 'a' + 'A';
15473                   if(board[0][c] >= WhiteKing) break;
15474                   if(c > i)
15475                       board[CASTLING][0] = c;
15476                   else
15477                       board[CASTLING][1] = c;
15478               }
15479         }
15480       }
15481       for(i=0; i<nrCastlingRights; i++)
15482         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15483     if (appData.debugMode) {
15484         fprintf(debugFP, "FEN castling rights:");
15485         for(i=0; i<nrCastlingRights; i++)
15486         fprintf(debugFP, " %d", board[CASTLING][i]);
15487         fprintf(debugFP, "\n");
15488     }
15489
15490       while(*p==' ') p++;
15491     }
15492
15493     /* read e.p. field in games that know e.p. capture */
15494     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15495        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15496       if(*p=='-') {
15497         p++; board[EP_STATUS] = EP_NONE;
15498       } else {
15499          char c = *p++ - AAA;
15500
15501          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15502          if(*p >= '0' && *p <='9') p++;
15503          board[EP_STATUS] = c;
15504       }
15505     }
15506
15507
15508     if(sscanf(p, "%d", &i) == 1) {
15509         FENrulePlies = i; /* 50-move ply counter */
15510         /* (The move number is still ignored)    */
15511     }
15512
15513     return TRUE;
15514 }
15515
15516 void
15517 EditPositionPasteFEN(char *fen)
15518 {
15519   if (fen != NULL) {
15520     Board initial_position;
15521
15522     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15523       DisplayError(_("Bad FEN position in clipboard"), 0);
15524       return ;
15525     } else {
15526       int savedBlackPlaysFirst = blackPlaysFirst;
15527       EditPositionEvent();
15528       blackPlaysFirst = savedBlackPlaysFirst;
15529       CopyBoard(boards[0], initial_position);
15530       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15531       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15532       DisplayBothClocks();
15533       DrawPosition(FALSE, boards[currentMove]);
15534     }
15535   }
15536 }
15537
15538 static char cseq[12] = "\\   ";
15539
15540 Boolean set_cont_sequence(char *new_seq)
15541 {
15542     int len;
15543     Boolean ret;
15544
15545     // handle bad attempts to set the sequence
15546         if (!new_seq)
15547                 return 0; // acceptable error - no debug
15548
15549     len = strlen(new_seq);
15550     ret = (len > 0) && (len < sizeof(cseq));
15551     if (ret)
15552       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15553     else if (appData.debugMode)
15554       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15555     return ret;
15556 }
15557
15558 /*
15559     reformat a source message so words don't cross the width boundary.  internal
15560     newlines are not removed.  returns the wrapped size (no null character unless
15561     included in source message).  If dest is NULL, only calculate the size required
15562     for the dest buffer.  lp argument indicats line position upon entry, and it's
15563     passed back upon exit.
15564 */
15565 int wrap(char *dest, char *src, int count, int width, int *lp)
15566 {
15567     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15568
15569     cseq_len = strlen(cseq);
15570     old_line = line = *lp;
15571     ansi = len = clen = 0;
15572
15573     for (i=0; i < count; i++)
15574     {
15575         if (src[i] == '\033')
15576             ansi = 1;
15577
15578         // if we hit the width, back up
15579         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15580         {
15581             // store i & len in case the word is too long
15582             old_i = i, old_len = len;
15583
15584             // find the end of the last word
15585             while (i && src[i] != ' ' && src[i] != '\n')
15586             {
15587                 i--;
15588                 len--;
15589             }
15590
15591             // word too long?  restore i & len before splitting it
15592             if ((old_i-i+clen) >= width)
15593             {
15594                 i = old_i;
15595                 len = old_len;
15596             }
15597
15598             // extra space?
15599             if (i && src[i-1] == ' ')
15600                 len--;
15601
15602             if (src[i] != ' ' && src[i] != '\n')
15603             {
15604                 i--;
15605                 if (len)
15606                     len--;
15607             }
15608
15609             // now append the newline and continuation sequence
15610             if (dest)
15611                 dest[len] = '\n';
15612             len++;
15613             if (dest)
15614                 strncpy(dest+len, cseq, cseq_len);
15615             len += cseq_len;
15616             line = cseq_len;
15617             clen = cseq_len;
15618             continue;
15619         }
15620
15621         if (dest)
15622             dest[len] = src[i];
15623         len++;
15624         if (!ansi)
15625             line++;
15626         if (src[i] == '\n')
15627             line = 0;
15628         if (src[i] == 'm')
15629             ansi = 0;
15630     }
15631     if (dest && appData.debugMode)
15632     {
15633         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15634             count, width, line, len, *lp);
15635         show_bytes(debugFP, src, count);
15636         fprintf(debugFP, "\ndest: ");
15637         show_bytes(debugFP, dest, len);
15638         fprintf(debugFP, "\n");
15639     }
15640     *lp = dest ? line : old_line;
15641
15642     return len;
15643 }
15644
15645 // [HGM] vari: routines for shelving variations
15646
15647 void
15648 PushTail(int firstMove, int lastMove)
15649 {
15650         int i, j, nrMoves = lastMove - firstMove;
15651
15652         if(appData.icsActive) { // only in local mode
15653                 forwardMostMove = currentMove; // mimic old ICS behavior
15654                 return;
15655         }
15656         if(storedGames >= MAX_VARIATIONS-1) return;
15657
15658         // push current tail of game on stack
15659         savedResult[storedGames] = gameInfo.result;
15660         savedDetails[storedGames] = gameInfo.resultDetails;
15661         gameInfo.resultDetails = NULL;
15662         savedFirst[storedGames] = firstMove;
15663         savedLast [storedGames] = lastMove;
15664         savedFramePtr[storedGames] = framePtr;
15665         framePtr -= nrMoves; // reserve space for the boards
15666         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15667             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15668             for(j=0; j<MOVE_LEN; j++)
15669                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15670             for(j=0; j<2*MOVE_LEN; j++)
15671                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15672             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15673             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15674             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15675             pvInfoList[firstMove+i-1].depth = 0;
15676             commentList[framePtr+i] = commentList[firstMove+i];
15677             commentList[firstMove+i] = NULL;
15678         }
15679
15680         storedGames++;
15681         forwardMostMove = firstMove; // truncate game so we can start variation
15682         if(storedGames == 1) GreyRevert(FALSE);
15683 }
15684
15685 Boolean
15686 PopTail(Boolean annotate)
15687 {
15688         int i, j, nrMoves;
15689         char buf[8000], moveBuf[20];
15690
15691         if(appData.icsActive) return FALSE; // only in local mode
15692         if(!storedGames) return FALSE; // sanity
15693         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15694
15695         storedGames--;
15696         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15697         nrMoves = savedLast[storedGames] - currentMove;
15698         if(annotate) {
15699                 int cnt = 10;
15700                 if(!WhiteOnMove(currentMove))
15701                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15702                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15703                 for(i=currentMove; i<forwardMostMove; i++) {
15704                         if(WhiteOnMove(i))
15705                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15706                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15707                         strcat(buf, moveBuf);
15708                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15709                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15710                 }
15711                 strcat(buf, ")");
15712         }
15713         for(i=1; i<=nrMoves; i++) { // copy last variation back
15714             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15715             for(j=0; j<MOVE_LEN; j++)
15716                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15717             for(j=0; j<2*MOVE_LEN; j++)
15718                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15719             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15720             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15721             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15722             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15723             commentList[currentMove+i] = commentList[framePtr+i];
15724             commentList[framePtr+i] = NULL;
15725         }
15726         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15727         framePtr = savedFramePtr[storedGames];
15728         gameInfo.result = savedResult[storedGames];
15729         if(gameInfo.resultDetails != NULL) {
15730             free(gameInfo.resultDetails);
15731       }
15732         gameInfo.resultDetails = savedDetails[storedGames];
15733         forwardMostMove = currentMove + nrMoves;
15734         if(storedGames == 0) GreyRevert(TRUE);
15735         return TRUE;
15736 }
15737
15738 void
15739 CleanupTail()
15740 {       // remove all shelved variations
15741         int i;
15742         for(i=0; i<storedGames; i++) {
15743             if(savedDetails[i])
15744                 free(savedDetails[i]);
15745             savedDetails[i] = NULL;
15746         }
15747         for(i=framePtr; i<MAX_MOVES; i++) {
15748                 if(commentList[i]) free(commentList[i]);
15749                 commentList[i] = NULL;
15750         }
15751         framePtr = MAX_MOVES-1;
15752         storedGames = 0;
15753 }
15754
15755 void
15756 LoadVariation(int index, char *text)
15757 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15758         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15759         int level = 0, move;
15760
15761         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15762         // first find outermost bracketing variation
15763         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15764             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15765                 if(*p == '{') wait = '}'; else
15766                 if(*p == '[') wait = ']'; else
15767                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15768                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15769             }
15770             if(*p == wait) wait = NULLCHAR; // closing ]} found
15771             p++;
15772         }
15773         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15774         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15775         end[1] = NULLCHAR; // clip off comment beyond variation
15776         ToNrEvent(currentMove-1);
15777         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15778         // kludge: use ParsePV() to append variation to game
15779         move = currentMove;
15780         ParsePV(start, TRUE);
15781         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15782         ClearPremoveHighlights();
15783         CommentPopDown();
15784         ToNrEvent(currentMove+1);
15785 }
15786