e12be26ba2599425bff7f7d8fac59aa7588dac1b
[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 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
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 /* [AS] */
313 static char * safeStrCpy( char * dst, const char * src, size_t count )
314 {
315     assert( dst != NULL );
316     assert( src != NULL );
317     assert( count > 0 );
318
319     strncpy( dst, src, count );
320     dst[ count-1 ] = '\0';
321     return dst;
322 }
323
324 /* Some compiler can't cast u64 to double
325  * This function do the job for us:
326
327  * We use the highest bit for cast, this only
328  * works if the highest bit is not
329  * in use (This should not happen)
330  *
331  * We used this for all compiler
332  */
333 double
334 u64ToDouble(u64 value)
335 {
336   double r;
337   u64 tmp = value & u64Const(0x7fffffffffffffff);
338   r = (double)(s64)tmp;
339   if (value & u64Const(0x8000000000000000))
340        r +=  9.2233720368547758080e18; /* 2^63 */
341  return r;
342 }
343
344 /* Fake up flags for now, as we aren't keeping track of castling
345    availability yet. [HGM] Change of logic: the flag now only
346    indicates the type of castlings allowed by the rule of the game.
347    The actual rights themselves are maintained in the array
348    castlingRights, as part of the game history, and are not probed
349    by this function.
350  */
351 int
352 PosFlags(index)
353 {
354   int flags = F_ALL_CASTLE_OK;
355   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
356   switch (gameInfo.variant) {
357   case VariantSuicide:
358     flags &= ~F_ALL_CASTLE_OK;
359   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
360     flags |= F_IGNORE_CHECK;
361   case VariantLosers:
362     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363     break;
364   case VariantAtomic:
365     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
366     break;
367   case VariantKriegspiel:
368     flags |= F_KRIEGSPIEL_CAPTURE;
369     break;
370   case VariantCapaRandom: 
371   case VariantFischeRandom:
372     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
373   case VariantNoCastle:
374   case VariantShatranj:
375   case VariantCourier:
376   case VariantMakruk:
377     flags &= ~F_ALL_CASTLE_OK;
378     break;
379   default:
380     break;
381   }
382   return flags;
383 }
384
385 FILE *gameFileFP, *debugFP;
386
387 /* 
388     [AS] Note: sometimes, the sscanf() function is used to parse the input
389     into a fixed-size buffer. Because of this, we must be prepared to
390     receive strings as long as the size of the input buffer, which is currently
391     set to 4K for Windows and 8K for the rest.
392     So, we must either allocate sufficiently large buffers here, or
393     reduce the size of the input buffer in the input reading part.
394 */
395
396 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
397 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
398 char thinkOutput1[MSG_SIZ*10];
399
400 ChessProgramState first, second;
401
402 /* premove variables */
403 int premoveToX = 0;
404 int premoveToY = 0;
405 int premoveFromX = 0;
406 int premoveFromY = 0;
407 int premovePromoChar = 0;
408 int gotPremove = 0;
409 Boolean alarmSounded;
410 /* end premove variables */
411
412 char *ics_prefix = "$";
413 int ics_type = ICS_GENERIC;
414
415 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
416 int pauseExamForwardMostMove = 0;
417 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
418 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
419 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
420 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
421 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
422 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
423 int whiteFlag = FALSE, blackFlag = FALSE;
424 int userOfferedDraw = FALSE;
425 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
426 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
427 int cmailMoveType[CMAIL_MAX_GAMES];
428 long ics_clock_paused = 0;
429 ProcRef icsPR = NoProc, cmailPR = NoProc;
430 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
431 GameMode gameMode = BeginningOfGame;
432 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
433 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
434 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
435 int hiddenThinkOutputState = 0; /* [AS] */
436 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
437 int adjudicateLossPlies = 6;
438 char white_holding[64], black_holding[64];
439 TimeMark lastNodeCountTime;
440 long lastNodeCount=0;
441 int have_sent_ICS_logon = 0;
442 int movesPerSession;
443 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
444 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
445 long timeControl_2; /* [AS] Allow separate time controls */
446 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
447 long timeRemaining[2][MAX_MOVES];
448 int matchGame = 0;
449 TimeMark programStartTime;
450 char ics_handle[MSG_SIZ];
451 int have_set_title = 0;
452
453 /* animateTraining preserves the state of appData.animate
454  * when Training mode is activated. This allows the
455  * response to be animated when appData.animate == TRUE and
456  * appData.animateDragging == TRUE.
457  */
458 Boolean animateTraining;
459
460 GameInfo gameInfo;
461
462 AppData appData;
463
464 Board boards[MAX_MOVES];
465 /* [HGM] Following 7 needed for accurate legality tests: */
466 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
467 signed char  initialRights[BOARD_FILES];
468 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
469 int   initialRulePlies, FENrulePlies;
470 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 int loadFlag = 0; 
472 int shuffleOpenings;
473 int mute; // mute all sounds
474
475 // [HGM] vari: next 12 to save and restore variations
476 #define MAX_VARIATIONS 10
477 int framePtr = MAX_MOVES-1; // points to free stack entry
478 int storedGames = 0;
479 int savedFirst[MAX_VARIATIONS];
480 int savedLast[MAX_VARIATIONS];
481 int savedFramePtr[MAX_VARIATIONS];
482 char *savedDetails[MAX_VARIATIONS];
483 ChessMove savedResult[MAX_VARIATIONS];
484
485 void PushTail P((int firstMove, int lastMove));
486 Boolean PopTail P((Boolean annotate));
487 void CleanupTail P((void));
488
489 ChessSquare  FIDEArray[2][BOARD_FILES] = {
490     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
492     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
493         BlackKing, BlackBishop, BlackKnight, BlackRook }
494 };
495
496 ChessSquare twoKingsArray[2][BOARD_FILES] = {
497     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
498         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
499     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
500         BlackKing, BlackKing, BlackKnight, BlackRook }
501 };
502
503 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
504     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
505         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
506     { BlackRook, BlackMan, BlackBishop, BlackQueen,
507         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
508 };
509
510 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
514         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
515 };
516
517 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
518     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
519         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
521         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 };
523
524 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
525     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
526         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackMan, BlackFerz,
528         BlackKing, BlackMan, BlackKnight, BlackRook }
529 };
530
531
532 #if (BOARD_FILES>=10)
533 ChessSquare ShogiArray[2][BOARD_FILES] = {
534     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
535         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
536     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
537         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
538 };
539
540 ChessSquare XiangqiArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
542         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
544         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare CapablancaArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
549         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
551         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
552 };
553
554 ChessSquare GreatArray[2][BOARD_FILES] = {
555     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
556         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
557     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
558         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
559 };
560
561 ChessSquare JanusArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
563         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
564     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
565         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
566 };
567
568 #ifdef GOTHIC
569 ChessSquare GothicArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
571         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
573         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
574 };
575 #else // !GOTHIC
576 #define GothicArray CapablancaArray
577 #endif // !GOTHIC
578
579 #ifdef FALCON
580 ChessSquare FalconArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
582         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
584         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
585 };
586 #else // !FALCON
587 #define FalconArray CapablancaArray
588 #endif // !FALCON
589
590 #else // !(BOARD_FILES>=10)
591 #define XiangqiPosition FIDEArray
592 #define CapablancaArray FIDEArray
593 #define GothicArray FIDEArray
594 #define GreatArray FIDEArray
595 #endif // !(BOARD_FILES>=10)
596
597 #if (BOARD_FILES>=12)
598 ChessSquare CourierArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
600         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
602         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
603 };
604 #else // !(BOARD_FILES>=12)
605 #define CourierArray CapablancaArray
606 #endif // !(BOARD_FILES>=12)
607
608
609 Board initialPosition;
610
611
612 /* Convert str to a rating. Checks for special cases of "----",
613
614    "++++", etc. Also strips ()'s */
615 int
616 string_to_rating(str)
617   char *str;
618 {
619   while(*str && !isdigit(*str)) ++str;
620   if (!*str)
621     return 0;   /* One of the special "no rating" cases */
622   else
623     return atoi(str);
624 }
625
626 void
627 ClearProgramStats()
628 {
629     /* Init programStats */
630     programStats.movelist[0] = 0;
631     programStats.depth = 0;
632     programStats.nr_moves = 0;
633     programStats.moves_left = 0;
634     programStats.nodes = 0;
635     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
636     programStats.score = 0;
637     programStats.got_only_move = 0;
638     programStats.got_fail = 0;
639     programStats.line_is_book = 0;
640 }
641
642 void
643 InitBackEnd1()
644 {
645     int matched, min, sec;
646
647     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
648     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
649
650     GetTimeMark(&programStartTime);
651     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
652
653     ClearProgramStats();
654     programStats.ok_to_send = 1;
655     programStats.seen_stat = 0;
656
657     /*
658      * Initialize game list
659      */
660     ListNew(&gameList);
661
662
663     /*
664      * Internet chess server status
665      */
666     if (appData.icsActive) {
667         appData.matchMode = FALSE;
668         appData.matchGames = 0;
669 #if ZIPPY       
670         appData.noChessProgram = !appData.zippyPlay;
671 #else
672         appData.zippyPlay = FALSE;
673         appData.zippyTalk = FALSE;
674         appData.noChessProgram = TRUE;
675 #endif
676         if (*appData.icsHelper != NULLCHAR) {
677             appData.useTelnet = TRUE;
678             appData.telnetProgram = appData.icsHelper;
679         }
680     } else {
681         appData.zippyTalk = appData.zippyPlay = FALSE;
682     }
683
684     /* [AS] Initialize pv info list [HGM] and game state */
685     {
686         int i, j;
687
688         for( i=0; i<=framePtr; i++ ) {
689             pvInfoList[i].depth = -1;
690             boards[i][EP_STATUS] = EP_NONE;
691             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
692         }
693     }
694
695     /*
696      * Parse timeControl resource
697      */
698     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
699                           appData.movesPerSession)) {
700         char buf[MSG_SIZ];
701         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
702         DisplayFatalError(buf, 0, 2);
703     }
704
705     /*
706      * Parse searchTime resource
707      */
708     if (*appData.searchTime != NULLCHAR) {
709         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
710         if (matched == 1) {
711             searchTime = min * 60;
712         } else if (matched == 2) {
713             searchTime = min * 60 + sec;
714         } else {
715             char buf[MSG_SIZ];
716             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
717             DisplayFatalError(buf, 0, 2);
718         }
719     }
720
721     /* [AS] Adjudication threshold */
722     adjudicateLossThreshold = appData.adjudicateLossThreshold;
723     
724     first.which = _("first");
725     second.which = _("second");
726     first.maybeThinking = second.maybeThinking = FALSE;
727     first.pr = second.pr = NoProc;
728     first.isr = second.isr = NULL;
729     first.sendTime = second.sendTime = 2;
730     first.sendDrawOffers = 1;
731     if (appData.firstPlaysBlack) {
732         first.twoMachinesColor = "black\n";
733         second.twoMachinesColor = "white\n";
734     } else {
735         first.twoMachinesColor = "white\n";
736         second.twoMachinesColor = "black\n";
737     }
738     first.program = appData.firstChessProgram;
739     second.program = appData.secondChessProgram;
740     first.host = appData.firstHost;
741     second.host = appData.secondHost;
742     first.dir = appData.firstDirectory;
743     second.dir = appData.secondDirectory;
744     first.other = &second;
745     second.other = &first;
746     first.initString = appData.initString;
747     second.initString = appData.secondInitString;
748     first.computerString = appData.firstComputerString;
749     second.computerString = appData.secondComputerString;
750     first.useSigint = second.useSigint = TRUE;
751     first.useSigterm = second.useSigterm = TRUE;
752     first.reuse = appData.reuseFirst;
753     second.reuse = appData.reuseSecond;
754     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
755     second.nps = appData.secondNPS;
756     first.useSetboard = second.useSetboard = FALSE;
757     first.useSAN = second.useSAN = FALSE;
758     first.usePing = second.usePing = FALSE;
759     first.lastPing = second.lastPing = 0;
760     first.lastPong = second.lastPong = 0;
761     first.usePlayother = second.usePlayother = FALSE;
762     first.useColors = second.useColors = TRUE;
763     first.useUsermove = second.useUsermove = FALSE;
764     first.sendICS = second.sendICS = FALSE;
765     first.sendName = second.sendName = appData.icsActive;
766     first.sdKludge = second.sdKludge = FALSE;
767     first.stKludge = second.stKludge = FALSE;
768     TidyProgramName(first.program, first.host, first.tidy);
769     TidyProgramName(second.program, second.host, second.tidy);
770     first.matchWins = second.matchWins = 0;
771     strcpy(first.variants, appData.variant);
772     strcpy(second.variants, appData.variant);
773     first.analysisSupport = second.analysisSupport = 2; /* detect */
774     first.analyzing = second.analyzing = FALSE;
775     first.initDone = second.initDone = FALSE;
776
777     /* New features added by Tord: */
778     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
779     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
780     /* End of new features added by Tord. */
781     first.fenOverride  = appData.fenOverride1;
782     second.fenOverride = appData.fenOverride2;
783
784     /* [HGM] time odds: set factor for each machine */
785     first.timeOdds  = appData.firstTimeOdds;
786     second.timeOdds = appData.secondTimeOdds;
787     { float norm = 1;
788         if(appData.timeOddsMode) {
789             norm = first.timeOdds;
790             if(norm > second.timeOdds) norm = second.timeOdds;
791         }
792         first.timeOdds /= norm;
793         second.timeOdds /= norm;
794     }
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     first.accumulateTC = appData.firstAccumulateTC;
798     second.accumulateTC = appData.secondAccumulateTC;
799     first.maxNrOfSessions = second.maxNrOfSessions = 1;
800
801     /* [HGM] debug */
802     first.debug = second.debug = FALSE;
803     first.supportsNPS = second.supportsNPS = UNKNOWN;
804
805     /* [HGM] options */
806     first.optionSettings  = appData.firstOptions;
807     second.optionSettings = appData.secondOptions;
808
809     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
810     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
811     first.isUCI = appData.firstIsUCI; /* [AS] */
812     second.isUCI = appData.secondIsUCI; /* [AS] */
813     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
814     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
815
816     if (appData.firstProtocolVersion > PROTOVER ||
817         appData.firstProtocolVersion < 1) {
818       char buf[MSG_SIZ];
819       sprintf(buf, _("protocol version %d not supported"),
820               appData.firstProtocolVersion);
821       DisplayFatalError(buf, 0, 2);
822     } else {
823       first.protocolVersion = appData.firstProtocolVersion;
824     }
825
826     if (appData.secondProtocolVersion > PROTOVER ||
827         appData.secondProtocolVersion < 1) {
828       char buf[MSG_SIZ];
829       sprintf(buf, _("protocol version %d not supported"),
830               appData.secondProtocolVersion);
831       DisplayFatalError(buf, 0, 2);
832     } else {
833       second.protocolVersion = appData.secondProtocolVersion;
834     }
835
836     if (appData.icsActive) {
837         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
838 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
839     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
840         appData.clockMode = FALSE;
841         first.sendTime = second.sendTime = 0;
842     }
843     
844 #if ZIPPY
845     /* Override some settings from environment variables, for backward
846        compatibility.  Unfortunately it's not feasible to have the env
847        vars just set defaults, at least in xboard.  Ugh.
848     */
849     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
850       ZippyInit();
851     }
852 #endif
853     
854     if (appData.noChessProgram) {
855         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
856         sprintf(programVersion, "%s", PACKAGE_STRING);
857     } else {
858       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
859       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
860       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
861     }
862
863     if (!appData.icsActive) {
864       char buf[MSG_SIZ];
865       /* Check for variants that are supported only in ICS mode,
866          or not at all.  Some that are accepted here nevertheless
867          have bugs; see comments below.
868       */
869       VariantClass variant = StringToVariant(appData.variant);
870       switch (variant) {
871       case VariantBughouse:     /* need four players and two boards */
872       case VariantKriegspiel:   /* need to hide pieces and move details */
873       /* case VariantFischeRandom: (Fabien: moved below) */
874         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
875         DisplayFatalError(buf, 0, 2);
876         return;
877
878       case VariantUnknown:
879       case VariantLoadable:
880       case Variant29:
881       case Variant30:
882       case Variant31:
883       case Variant32:
884       case Variant33:
885       case Variant34:
886       case Variant35:
887       case Variant36:
888       default:
889         sprintf(buf, _("Unknown variant name %s"), appData.variant);
890         DisplayFatalError(buf, 0, 2);
891         return;
892
893       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
894       case VariantFairy:      /* [HGM] TestLegality definitely off! */
895       case VariantGothic:     /* [HGM] should work */
896       case VariantCapablanca: /* [HGM] should work */
897       case VariantCourier:    /* [HGM] initial forced moves not implemented */
898       case VariantShogi:      /* [HGM] could still mate with pawn drop */
899       case VariantKnightmate: /* [HGM] should work */
900       case VariantCylinder:   /* [HGM] untested */
901       case VariantFalcon:     /* [HGM] untested */
902       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
903                                  offboard interposition not understood */
904       case VariantNormal:     /* definitely works! */
905       case VariantWildCastle: /* pieces not automatically shuffled */
906       case VariantNoCastle:   /* pieces not automatically shuffled */
907       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
908       case VariantLosers:     /* should work except for win condition,
909                                  and doesn't know captures are mandatory */
910       case VariantSuicide:    /* should work except for win condition,
911                                  and doesn't know captures are mandatory */
912       case VariantGiveaway:   /* should work except for win condition,
913                                  and doesn't know captures are mandatory */
914       case VariantTwoKings:   /* should work */
915       case VariantAtomic:     /* should work except for win condition */
916       case Variant3Check:     /* should work except for win condition */
917       case VariantShatranj:   /* should work except for all win conditions */
918       case VariantMakruk:     /* should work except for daw countdown */
919       case VariantBerolina:   /* might work if TestLegality is off */
920       case VariantCapaRandom: /* should work */
921       case VariantJanus:      /* should work */
922       case VariantSuper:      /* experimental */
923       case VariantGreat:      /* experimental, requires legality testing to be off */
924         break;
925       }
926     }
927
928     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
929     InitEngineUCI( installDir, &second );
930 }
931
932 int NextIntegerFromString( char ** str, long * value )
933 {
934     int result = -1;
935     char * s = *str;
936
937     while( *s == ' ' || *s == '\t' ) {
938         s++;
939     }
940
941     *value = 0;
942
943     if( *s >= '0' && *s <= '9' ) {
944         while( *s >= '0' && *s <= '9' ) {
945             *value = *value * 10 + (*s - '0');
946             s++;
947         }
948
949         result = 0;
950     }
951
952     *str = s;
953
954     return result;
955 }
956
957 int NextTimeControlFromString( char ** str, long * value )
958 {
959     long temp;
960     int result = NextIntegerFromString( str, &temp );
961
962     if( result == 0 ) {
963         *value = temp * 60; /* Minutes */
964         if( **str == ':' ) {
965             (*str)++;
966             result = NextIntegerFromString( str, &temp );
967             *value += temp; /* Seconds */
968         }
969     }
970
971     return result;
972 }
973
974 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
975 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
976     int result = -1, type = 0; long temp, temp2;
977
978     if(**str != ':') return -1; // old params remain in force!
979     (*str)++;
980     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
981     if( NextIntegerFromString( str, &temp ) ) return -1;
982     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
983
984     if(**str != '/') {
985         /* time only: incremental or sudden-death time control */
986         if(**str == '+') { /* increment follows; read it */
987             (*str)++;
988             if(**str == '!') type = *(*str)++; // Bronstein TC
989             if(result = NextIntegerFromString( str, &temp2)) return -1;
990             *inc = temp2 * 1000;
991         } else *inc = 0;
992         *moves = 0; *tc = temp * 1000; *incType = type;
993         return 0;
994     }
995
996     (*str)++; /* classical time control */
997     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
998
999     if(result == 0) {
1000         *moves = temp;
1001         *tc    = temp2 * 1000;
1002         *inc   = 0;
1003         *incType = type;
1004     }
1005     return result;
1006 }
1007
1008 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1009 {   /* [HGM] get time to add from the multi-session time-control string */
1010     int incType, moves=1; /* kludge to force reading of first session */
1011     long time, increment;
1012     char *s = tcString;
1013
1014     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1015     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1016     do {
1017         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1018         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1019         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1020         if(movenr == -1) return time;    /* last move before new session     */
1021         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1022         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1023         if(!moves) return increment;     /* current session is incremental   */
1024         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1025     } while(movenr >= -1);               /* try again for next session       */
1026
1027     return 0; // no new time quota on this move
1028 }
1029
1030 int
1031 ParseTimeControl(tc, ti, mps)
1032      char *tc;
1033      int ti;
1034      int mps;
1035 {
1036   long tc1;
1037   long tc2;
1038   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1039   int min, sec=0;
1040   
1041   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1042   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1043       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1044   if(ti > 0) {
1045     if(mps)
1046       sprintf(buf, ":%d/%s+%d", mps, mytc, ti);
1047     else sprintf(buf, ":%s+%d", mytc, ti);
1048   } else {
1049     if(mps)
1050              sprintf(buf, ":%d/%s", mps, mytc);
1051     else sprintf(buf, ":%s", mytc);
1052   }
1053   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1054   
1055   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1056     return FALSE;
1057   }
1058   
1059   if( *tc == '/' ) {
1060     /* Parse second time control */
1061     tc++;
1062     
1063     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1064       return FALSE;
1065     }
1066     
1067     if( tc2 == 0 ) {
1068       return FALSE;
1069     }
1070     
1071     timeControl_2 = tc2 * 1000;
1072   }
1073   else {
1074     timeControl_2 = 0;
1075   }
1076   
1077   if( tc1 == 0 ) {
1078     return FALSE;
1079   }
1080   
1081   timeControl = tc1 * 1000;
1082   
1083   if (ti >= 0) {
1084     timeIncrement = ti * 1000;  /* convert to ms */
1085     movesPerSession = 0;
1086   } else {
1087     timeIncrement = 0;
1088     movesPerSession = mps;
1089   }
1090   return TRUE;
1091 }
1092
1093 void
1094 InitBackEnd2()
1095 {
1096     if (appData.debugMode) {
1097         fprintf(debugFP, "%s\n", programVersion);
1098     }
1099
1100     set_cont_sequence(appData.wrapContSeq);
1101     if (appData.matchGames > 0) {
1102         appData.matchMode = TRUE;
1103     } else if (appData.matchMode) {
1104         appData.matchGames = 1;
1105     }
1106     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1107         appData.matchGames = appData.sameColorGames;
1108     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1109         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1110         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1111     }
1112     Reset(TRUE, FALSE);
1113     if (appData.noChessProgram || first.protocolVersion == 1) {
1114       InitBackEnd3();
1115     } else {
1116       /* kludge: allow timeout for initial "feature" commands */
1117       FreezeUI();
1118       DisplayMessage("", _("Starting chess program"));
1119       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1120     }
1121 }
1122
1123 void
1124 InitBackEnd3 P((void))
1125 {
1126     GameMode initialMode;
1127     char buf[MSG_SIZ];
1128     int err;
1129
1130     InitChessProgram(&first, startedFromSetupPosition);
1131
1132     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1133         free(programVersion);
1134         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1135         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1136     }
1137
1138     if (appData.icsActive) {
1139 #ifdef WIN32
1140         /* [DM] Make a console window if needed [HGM] merged ifs */
1141         ConsoleCreate(); 
1142 #endif
1143         err = establish();
1144         if (err != 0) {
1145             if (*appData.icsCommPort != NULLCHAR) {
1146                 sprintf(buf, _("Could not open comm port %s"),  
1147                         appData.icsCommPort);
1148             } else {
1149                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1150                         appData.icsHost, appData.icsPort);
1151             }
1152             DisplayFatalError(buf, err, 1);
1153             return;
1154         }
1155         SetICSMode();
1156         telnetISR =
1157           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1158         fromUserISR =
1159           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1160         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1161             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1162     } else if (appData.noChessProgram) {
1163         SetNCPMode();
1164     } else {
1165         SetGNUMode();
1166     }
1167
1168     if (*appData.cmailGameName != NULLCHAR) {
1169         SetCmailMode();
1170         OpenLoopback(&cmailPR);
1171         cmailISR =
1172           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1173     }
1174     
1175     ThawUI();
1176     DisplayMessage("", "");
1177     if (StrCaseCmp(appData.initialMode, "") == 0) {
1178       initialMode = BeginningOfGame;
1179     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1180       initialMode = TwoMachinesPlay;
1181     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1182       initialMode = AnalyzeFile; 
1183     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1184       initialMode = AnalyzeMode;
1185     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1186       initialMode = MachinePlaysWhite;
1187     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1188       initialMode = MachinePlaysBlack;
1189     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1190       initialMode = EditGame;
1191     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1192       initialMode = EditPosition;
1193     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1194       initialMode = Training;
1195     } else {
1196       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1197       DisplayFatalError(buf, 0, 2);
1198       return;
1199     }
1200
1201     if (appData.matchMode) {
1202         /* Set up machine vs. machine match */
1203         if (appData.noChessProgram) {
1204             DisplayFatalError(_("Can't have a match with no chess programs"),
1205                               0, 2);
1206             return;
1207         }
1208         matchMode = TRUE;
1209         matchGame = 1;
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             int index = appData.loadGameIndex; // [HGM] autoinc
1212             if(index<0) lastIndex = index = 1;
1213             if (!LoadGameFromFile(appData.loadGameFile,
1214                                   index,
1215                                   appData.loadGameFile, FALSE)) {
1216                 DisplayFatalError(_("Bad game file"), 0, 1);
1217                 return;
1218             }
1219         } else if (*appData.loadPositionFile != NULLCHAR) {
1220             int index = appData.loadPositionIndex; // [HGM] autoinc
1221             if(index<0) lastIndex = index = 1;
1222             if (!LoadPositionFromFile(appData.loadPositionFile,
1223                                       index,
1224                                       appData.loadPositionFile)) {
1225                 DisplayFatalError(_("Bad position file"), 0, 1);
1226                 return;
1227             }
1228         }
1229         TwoMachinesEvent();
1230     } else if (*appData.cmailGameName != NULLCHAR) {
1231         /* Set up cmail mode */
1232         ReloadCmailMsgEvent(TRUE);
1233     } else {
1234         /* Set up other modes */
1235         if (initialMode == AnalyzeFile) {
1236           if (*appData.loadGameFile == NULLCHAR) {
1237             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1238             return;
1239           }
1240         }
1241         if (*appData.loadGameFile != NULLCHAR) {
1242             (void) LoadGameFromFile(appData.loadGameFile,
1243                                     appData.loadGameIndex,
1244                                     appData.loadGameFile, TRUE);
1245         } else if (*appData.loadPositionFile != NULLCHAR) {
1246             (void) LoadPositionFromFile(appData.loadPositionFile,
1247                                         appData.loadPositionIndex,
1248                                         appData.loadPositionFile);
1249             /* [HGM] try to make self-starting even after FEN load */
1250             /* to allow automatic setup of fairy variants with wtm */
1251             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1252                 gameMode = BeginningOfGame;
1253                 setboardSpoiledMachineBlack = 1;
1254             }
1255             /* [HGM] loadPos: make that every new game uses the setup */
1256             /* from file as long as we do not switch variant          */
1257             if(!blackPlaysFirst) {
1258                 startedFromPositionFile = TRUE;
1259                 CopyBoard(filePosition, boards[0]);
1260             }
1261         }
1262         if (initialMode == AnalyzeMode) {
1263           if (appData.noChessProgram) {
1264             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1265             return;
1266           }
1267           if (appData.icsActive) {
1268             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1269             return;
1270           }
1271           AnalyzeModeEvent();
1272         } else if (initialMode == AnalyzeFile) {
1273           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1274           ShowThinkingEvent();
1275           AnalyzeFileEvent();
1276           AnalysisPeriodicEvent(1);
1277         } else if (initialMode == MachinePlaysWhite) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           MachineWhiteEvent();
1289         } else if (initialMode == MachinePlaysBlack) {
1290           if (appData.noChessProgram) {
1291             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1292                               0, 2);
1293             return;
1294           }
1295           if (appData.icsActive) {
1296             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1297                               0, 2);
1298             return;
1299           }
1300           MachineBlackEvent();
1301         } else if (initialMode == TwoMachinesPlay) {
1302           if (appData.noChessProgram) {
1303             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1304                               0, 2);
1305             return;
1306           }
1307           if (appData.icsActive) {
1308             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1309                               0, 2);
1310             return;
1311           }
1312           TwoMachinesEvent();
1313         } else if (initialMode == EditGame) {
1314           EditGameEvent();
1315         } else if (initialMode == EditPosition) {
1316           EditPositionEvent();
1317         } else if (initialMode == Training) {
1318           if (*appData.loadGameFile == NULLCHAR) {
1319             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1320             return;
1321           }
1322           TrainingEvent();
1323         }
1324     }
1325 }
1326
1327 /*
1328  * Establish will establish a contact to a remote host.port.
1329  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1330  *  used to talk to the host.
1331  * Returns 0 if okay, error code if not.
1332  */
1333 int
1334 establish()
1335 {
1336     char buf[MSG_SIZ];
1337
1338     if (*appData.icsCommPort != NULLCHAR) {
1339         /* Talk to the host through a serial comm port */
1340         return OpenCommPort(appData.icsCommPort, &icsPR);
1341
1342     } else if (*appData.gateway != NULLCHAR) {
1343         if (*appData.remoteShell == NULLCHAR) {
1344             /* Use the rcmd protocol to run telnet program on a gateway host */
1345             snprintf(buf, sizeof(buf), "%s %s %s",
1346                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1347             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1348
1349         } else {
1350             /* Use the rsh program to run telnet program on a gateway host */
1351             if (*appData.remoteUser == NULLCHAR) {
1352                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1353                         appData.gateway, appData.telnetProgram,
1354                         appData.icsHost, appData.icsPort);
1355             } else {
1356                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1357                         appData.remoteShell, appData.gateway, 
1358                         appData.remoteUser, appData.telnetProgram,
1359                         appData.icsHost, appData.icsPort);
1360             }
1361             return StartChildProcess(buf, "", &icsPR);
1362
1363         }
1364     } else if (appData.useTelnet) {
1365         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1366
1367     } else {
1368         /* TCP socket interface differs somewhat between
1369            Unix and NT; handle details in the front end.
1370            */
1371         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1372     }
1373 }
1374
1375 void EscapeExpand(char *p, char *q)
1376 {       // [HGM] initstring: routine to shape up string arguments
1377         while(*p++ = *q++) if(p[-1] == '\\')
1378             switch(*q++) {
1379                 case 'n': p[-1] = '\n'; break;
1380                 case 'r': p[-1] = '\r'; break;
1381                 case 't': p[-1] = '\t'; break;
1382                 case '\\': p[-1] = '\\'; break;
1383                 case 0: *p = 0; return;
1384                 default: p[-1] = q[-1]; break;
1385             }
1386 }
1387
1388 void
1389 show_bytes(fp, buf, count)
1390      FILE *fp;
1391      char *buf;
1392      int count;
1393 {
1394     while (count--) {
1395         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1396             fprintf(fp, "\\%03o", *buf & 0xff);
1397         } else {
1398             putc(*buf, fp);
1399         }
1400         buf++;
1401     }
1402     fflush(fp);
1403 }
1404
1405 /* Returns an errno value */
1406 int
1407 OutputMaybeTelnet(pr, message, count, outError)
1408      ProcRef pr;
1409      char *message;
1410      int count;
1411      int *outError;
1412 {
1413     char buf[8192], *p, *q, *buflim;
1414     int left, newcount, outcount;
1415
1416     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1417         *appData.gateway != NULLCHAR) {
1418         if (appData.debugMode) {
1419             fprintf(debugFP, ">ICS: ");
1420             show_bytes(debugFP, message, count);
1421             fprintf(debugFP, "\n");
1422         }
1423         return OutputToProcess(pr, message, count, outError);
1424     }
1425
1426     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1427     p = message;
1428     q = buf;
1429     left = count;
1430     newcount = 0;
1431     while (left) {
1432         if (q >= buflim) {
1433             if (appData.debugMode) {
1434                 fprintf(debugFP, ">ICS: ");
1435                 show_bytes(debugFP, buf, newcount);
1436                 fprintf(debugFP, "\n");
1437             }
1438             outcount = OutputToProcess(pr, buf, newcount, outError);
1439             if (outcount < newcount) return -1; /* to be sure */
1440             q = buf;
1441             newcount = 0;
1442         }
1443         if (*p == '\n') {
1444             *q++ = '\r';
1445             newcount++;
1446         } else if (((unsigned char) *p) == TN_IAC) {
1447             *q++ = (char) TN_IAC;
1448             newcount ++;
1449         }
1450         *q++ = *p++;
1451         newcount++;
1452         left--;
1453     }
1454     if (appData.debugMode) {
1455         fprintf(debugFP, ">ICS: ");
1456         show_bytes(debugFP, buf, newcount);
1457         fprintf(debugFP, "\n");
1458     }
1459     outcount = OutputToProcess(pr, buf, newcount, outError);
1460     if (outcount < newcount) return -1; /* to be sure */
1461     return count;
1462 }
1463
1464 void
1465 read_from_player(isr, closure, message, count, error)
1466      InputSourceRef isr;
1467      VOIDSTAR closure;
1468      char *message;
1469      int count;
1470      int error;
1471 {
1472     int outError, outCount;
1473     static int gotEof = 0;
1474
1475     /* Pass data read from player on to ICS */
1476     if (count > 0) {
1477         gotEof = 0;
1478         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1479         if (outCount < count) {
1480             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481         }
1482     } else if (count < 0) {
1483         RemoveInputSource(isr);
1484         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1485     } else if (gotEof++ > 0) {
1486         RemoveInputSource(isr);
1487         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1488     }
1489 }
1490
1491 void
1492 KeepAlive()
1493 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1494     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1495     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1496     SendToICS("date\n");
1497     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1498 }
1499
1500 /* added routine for printf style output to ics */
1501 void ics_printf(char *format, ...)
1502 {
1503     char buffer[MSG_SIZ];
1504     va_list args;
1505
1506     va_start(args, format);
1507     vsnprintf(buffer, sizeof(buffer), format, args);
1508     buffer[sizeof(buffer)-1] = '\0';
1509     SendToICS(buffer);
1510     va_end(args);
1511 }
1512
1513 void
1514 SendToICS(s)
1515      char *s;
1516 {
1517     int count, outCount, outError;
1518
1519     if (icsPR == NULL) return;
1520
1521     count = strlen(s);
1522     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1523     if (outCount < count) {
1524         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1525     }
1526 }
1527
1528 /* This is used for sending logon scripts to the ICS. Sending
1529    without a delay causes problems when using timestamp on ICC
1530    (at least on my machine). */
1531 void
1532 SendToICSDelayed(s,msdelay)
1533      char *s;
1534      long msdelay;
1535 {
1536     int count, outCount, outError;
1537
1538     if (icsPR == NULL) return;
1539
1540     count = strlen(s);
1541     if (appData.debugMode) {
1542         fprintf(debugFP, ">ICS: ");
1543         show_bytes(debugFP, s, count);
1544         fprintf(debugFP, "\n");
1545     }
1546     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1547                                       msdelay);
1548     if (outCount < count) {
1549         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1550     }
1551 }
1552
1553
1554 /* Remove all highlighting escape sequences in s
1555    Also deletes any suffix starting with '(' 
1556    */
1557 char *
1558 StripHighlightAndTitle(s)
1559      char *s;
1560 {
1561     static char retbuf[MSG_SIZ];
1562     char *p = retbuf;
1563
1564     while (*s != NULLCHAR) {
1565         while (*s == '\033') {
1566             while (*s != NULLCHAR && !isalpha(*s)) s++;
1567             if (*s != NULLCHAR) s++;
1568         }
1569         while (*s != NULLCHAR && *s != '\033') {
1570             if (*s == '(' || *s == '[') {
1571                 *p = NULLCHAR;
1572                 return retbuf;
1573             }
1574             *p++ = *s++;
1575         }
1576     }
1577     *p = NULLCHAR;
1578     return retbuf;
1579 }
1580
1581 /* Remove all highlighting escape sequences in s */
1582 char *
1583 StripHighlight(s)
1584      char *s;
1585 {
1586     static char retbuf[MSG_SIZ];
1587     char *p = retbuf;
1588
1589     while (*s != NULLCHAR) {
1590         while (*s == '\033') {
1591             while (*s != NULLCHAR && !isalpha(*s)) s++;
1592             if (*s != NULLCHAR) s++;
1593         }
1594         while (*s != NULLCHAR && *s != '\033') {
1595             *p++ = *s++;
1596         }
1597     }
1598     *p = NULLCHAR;
1599     return retbuf;
1600 }
1601
1602 char *variantNames[] = VARIANT_NAMES;
1603 char *
1604 VariantName(v)
1605      VariantClass v;
1606 {
1607     return variantNames[v];
1608 }
1609
1610
1611 /* Identify a variant from the strings the chess servers use or the
1612    PGN Variant tag names we use. */
1613 VariantClass
1614 StringToVariant(e)
1615      char *e;
1616 {
1617     char *p;
1618     int wnum = -1;
1619     VariantClass v = VariantNormal;
1620     int i, found = FALSE;
1621     char buf[MSG_SIZ];
1622
1623     if (!e) return v;
1624
1625     /* [HGM] skip over optional board-size prefixes */
1626     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1627         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1628         while( *e++ != '_');
1629     }
1630
1631     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1632         v = VariantNormal;
1633         found = TRUE;
1634     } else
1635     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1636       if (StrCaseStr(e, variantNames[i])) {
1637         v = (VariantClass) i;
1638         found = TRUE;
1639         break;
1640       }
1641     }
1642
1643     if (!found) {
1644       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1645           || StrCaseStr(e, "wild/fr") 
1646           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1647         v = VariantFischeRandom;
1648       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1649                  (i = 1, p = StrCaseStr(e, "w"))) {
1650         p += i;
1651         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1652         if (isdigit(*p)) {
1653           wnum = atoi(p);
1654         } else {
1655           wnum = -1;
1656         }
1657         switch (wnum) {
1658         case 0: /* FICS only, actually */
1659         case 1:
1660           /* Castling legal even if K starts on d-file */
1661           v = VariantWildCastle;
1662           break;
1663         case 2:
1664         case 3:
1665         case 4:
1666           /* Castling illegal even if K & R happen to start in
1667              normal positions. */
1668           v = VariantNoCastle;
1669           break;
1670         case 5:
1671         case 7:
1672         case 8:
1673         case 10:
1674         case 11:
1675         case 12:
1676         case 13:
1677         case 14:
1678         case 15:
1679         case 18:
1680         case 19:
1681           /* Castling legal iff K & R start in normal positions */
1682           v = VariantNormal;
1683           break;
1684         case 6:
1685         case 20:
1686         case 21:
1687           /* Special wilds for position setup; unclear what to do here */
1688           v = VariantLoadable;
1689           break;
1690         case 9:
1691           /* Bizarre ICC game */
1692           v = VariantTwoKings;
1693           break;
1694         case 16:
1695           v = VariantKriegspiel;
1696           break;
1697         case 17:
1698           v = VariantLosers;
1699           break;
1700         case 22:
1701           v = VariantFischeRandom;
1702           break;
1703         case 23:
1704           v = VariantCrazyhouse;
1705           break;
1706         case 24:
1707           v = VariantBughouse;
1708           break;
1709         case 25:
1710           v = Variant3Check;
1711           break;
1712         case 26:
1713           /* Not quite the same as FICS suicide! */
1714           v = VariantGiveaway;
1715           break;
1716         case 27:
1717           v = VariantAtomic;
1718           break;
1719         case 28:
1720           v = VariantShatranj;
1721           break;
1722
1723         /* Temporary names for future ICC types.  The name *will* change in 
1724            the next xboard/WinBoard release after ICC defines it. */
1725         case 29:
1726           v = Variant29;
1727           break;
1728         case 30:
1729           v = Variant30;
1730           break;
1731         case 31:
1732           v = Variant31;
1733           break;
1734         case 32:
1735           v = Variant32;
1736           break;
1737         case 33:
1738           v = Variant33;
1739           break;
1740         case 34:
1741           v = Variant34;
1742           break;
1743         case 35:
1744           v = Variant35;
1745           break;
1746         case 36:
1747           v = Variant36;
1748           break;
1749         case 37:
1750           v = VariantShogi;
1751           break;
1752         case 38:
1753           v = VariantXiangqi;
1754           break;
1755         case 39:
1756           v = VariantCourier;
1757           break;
1758         case 40:
1759           v = VariantGothic;
1760           break;
1761         case 41:
1762           v = VariantCapablanca;
1763           break;
1764         case 42:
1765           v = VariantKnightmate;
1766           break;
1767         case 43:
1768           v = VariantFairy;
1769           break;
1770         case 44:
1771           v = VariantCylinder;
1772           break;
1773         case 45:
1774           v = VariantFalcon;
1775           break;
1776         case 46:
1777           v = VariantCapaRandom;
1778           break;
1779         case 47:
1780           v = VariantBerolina;
1781           break;
1782         case 48:
1783           v = VariantJanus;
1784           break;
1785         case 49:
1786           v = VariantSuper;
1787           break;
1788         case 50:
1789           v = VariantGreat;
1790           break;
1791         case -1:
1792           /* Found "wild" or "w" in the string but no number;
1793              must assume it's normal chess. */
1794           v = VariantNormal;
1795           break;
1796         default:
1797           sprintf(buf, _("Unknown wild type %d"), wnum);
1798           DisplayError(buf, 0);
1799           v = VariantUnknown;
1800           break;
1801         }
1802       }
1803     }
1804     if (appData.debugMode) {
1805       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1806               e, wnum, VariantName(v));
1807     }
1808     return v;
1809 }
1810
1811 static int leftover_start = 0, leftover_len = 0;
1812 char star_match[STAR_MATCH_N][MSG_SIZ];
1813
1814 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1815    advance *index beyond it, and set leftover_start to the new value of
1816    *index; else return FALSE.  If pattern contains the character '*', it
1817    matches any sequence of characters not containing '\r', '\n', or the
1818    character following the '*' (if any), and the matched sequence(s) are
1819    copied into star_match.
1820    */
1821 int
1822 looking_at(buf, index, pattern)
1823      char *buf;
1824      int *index;
1825      char *pattern;
1826 {
1827     char *bufp = &buf[*index], *patternp = pattern;
1828     int star_count = 0;
1829     char *matchp = star_match[0];
1830     
1831     for (;;) {
1832         if (*patternp == NULLCHAR) {
1833             *index = leftover_start = bufp - buf;
1834             *matchp = NULLCHAR;
1835             return TRUE;
1836         }
1837         if (*bufp == NULLCHAR) return FALSE;
1838         if (*patternp == '*') {
1839             if (*bufp == *(patternp + 1)) {
1840                 *matchp = NULLCHAR;
1841                 matchp = star_match[++star_count];
1842                 patternp += 2;
1843                 bufp++;
1844                 continue;
1845             } else if (*bufp == '\n' || *bufp == '\r') {
1846                 patternp++;
1847                 if (*patternp == NULLCHAR)
1848                   continue;
1849                 else
1850                   return FALSE;
1851             } else {
1852                 *matchp++ = *bufp++;
1853                 continue;
1854             }
1855         }
1856         if (*patternp != *bufp) return FALSE;
1857         patternp++;
1858         bufp++;
1859     }
1860 }
1861
1862 void
1863 SendToPlayer(data, length)
1864      char *data;
1865      int length;
1866 {
1867     int error, outCount;
1868     outCount = OutputToProcess(NoProc, data, length, &error);
1869     if (outCount < length) {
1870         DisplayFatalError(_("Error writing to display"), error, 1);
1871     }
1872 }
1873
1874 void
1875 PackHolding(packed, holding)
1876      char packed[];
1877      char *holding;
1878 {
1879     char *p = holding;
1880     char *q = packed;
1881     int runlength = 0;
1882     int curr = 9999;
1883     do {
1884         if (*p == curr) {
1885             runlength++;
1886         } else {
1887             switch (runlength) {
1888               case 0:
1889                 break;
1890               case 1:
1891                 *q++ = curr;
1892                 break;
1893               case 2:
1894                 *q++ = curr;
1895                 *q++ = curr;
1896                 break;
1897               default:
1898                 sprintf(q, "%d", runlength);
1899                 while (*q) q++;
1900                 *q++ = curr;
1901                 break;
1902             }
1903             runlength = 1;
1904             curr = *p;
1905         }
1906     } while (*p++);
1907     *q = NULLCHAR;
1908 }
1909
1910 /* Telnet protocol requests from the front end */
1911 void
1912 TelnetRequest(ddww, option)
1913      unsigned char ddww, option;
1914 {
1915     unsigned char msg[3];
1916     int outCount, outError;
1917
1918     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1919
1920     if (appData.debugMode) {
1921         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1922         switch (ddww) {
1923           case TN_DO:
1924             ddwwStr = "DO";
1925             break;
1926           case TN_DONT:
1927             ddwwStr = "DONT";
1928             break;
1929           case TN_WILL:
1930             ddwwStr = "WILL";
1931             break;
1932           case TN_WONT:
1933             ddwwStr = "WONT";
1934             break;
1935           default:
1936             ddwwStr = buf1;
1937             sprintf(buf1, "%d", ddww);
1938             break;
1939         }
1940         switch (option) {
1941           case TN_ECHO:
1942             optionStr = "ECHO";
1943             break;
1944           default:
1945             optionStr = buf2;
1946             sprintf(buf2, "%d", option);
1947             break;
1948         }
1949         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1950     }
1951     msg[0] = TN_IAC;
1952     msg[1] = ddww;
1953     msg[2] = option;
1954     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1955     if (outCount < 3) {
1956         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1957     }
1958 }
1959
1960 void
1961 DoEcho()
1962 {
1963     if (!appData.icsActive) return;
1964     TelnetRequest(TN_DO, TN_ECHO);
1965 }
1966
1967 void
1968 DontEcho()
1969 {
1970     if (!appData.icsActive) return;
1971     TelnetRequest(TN_DONT, TN_ECHO);
1972 }
1973
1974 void
1975 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1976 {
1977     /* put the holdings sent to us by the server on the board holdings area */
1978     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1979     char p;
1980     ChessSquare piece;
1981
1982     if(gameInfo.holdingsWidth < 2)  return;
1983     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1984         return; // prevent overwriting by pre-board holdings
1985
1986     if( (int)lowestPiece >= BlackPawn ) {
1987         holdingsColumn = 0;
1988         countsColumn = 1;
1989         holdingsStartRow = BOARD_HEIGHT-1;
1990         direction = -1;
1991     } else {
1992         holdingsColumn = BOARD_WIDTH-1;
1993         countsColumn = BOARD_WIDTH-2;
1994         holdingsStartRow = 0;
1995         direction = 1;
1996     }
1997
1998     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1999         board[i][holdingsColumn] = EmptySquare;
2000         board[i][countsColumn]   = (ChessSquare) 0;
2001     }
2002     while( (p=*holdings++) != NULLCHAR ) {
2003         piece = CharToPiece( ToUpper(p) );
2004         if(piece == EmptySquare) continue;
2005         /*j = (int) piece - (int) WhitePawn;*/
2006         j = PieceToNumber(piece);
2007         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2008         if(j < 0) continue;               /* should not happen */
2009         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2010         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2011         board[holdingsStartRow+j*direction][countsColumn]++;
2012     }
2013 }
2014
2015
2016 void
2017 VariantSwitch(Board board, VariantClass newVariant)
2018 {
2019    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2020    static Board oldBoard;
2021
2022    startedFromPositionFile = FALSE;
2023    if(gameInfo.variant == newVariant) return;
2024
2025    /* [HGM] This routine is called each time an assignment is made to
2026     * gameInfo.variant during a game, to make sure the board sizes
2027     * are set to match the new variant. If that means adding or deleting
2028     * holdings, we shift the playing board accordingly
2029     * This kludge is needed because in ICS observe mode, we get boards
2030     * of an ongoing game without knowing the variant, and learn about the
2031     * latter only later. This can be because of the move list we requested,
2032     * in which case the game history is refilled from the beginning anyway,
2033     * but also when receiving holdings of a crazyhouse game. In the latter
2034     * case we want to add those holdings to the already received position.
2035     */
2036
2037    
2038    if (appData.debugMode) {
2039      fprintf(debugFP, "Switch board from %s to %s\n",
2040              VariantName(gameInfo.variant), VariantName(newVariant));
2041      setbuf(debugFP, NULL);
2042    }
2043    shuffleOpenings = 0;       /* [HGM] shuffle */
2044    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2045    switch(newVariant) 
2046      {
2047      case VariantShogi:
2048        newWidth = 9;  newHeight = 9;
2049        gameInfo.holdingsSize = 7;
2050      case VariantBughouse:
2051      case VariantCrazyhouse:
2052        newHoldingsWidth = 2; break;
2053      case VariantGreat:
2054        newWidth = 10;
2055      case VariantSuper:
2056        newHoldingsWidth = 2;
2057        gameInfo.holdingsSize = 8;
2058        break;
2059      case VariantGothic:
2060      case VariantCapablanca:
2061      case VariantCapaRandom:
2062        newWidth = 10;
2063      default:
2064        newHoldingsWidth = gameInfo.holdingsSize = 0;
2065      };
2066    
2067    if(newWidth  != gameInfo.boardWidth  ||
2068       newHeight != gameInfo.boardHeight ||
2069       newHoldingsWidth != gameInfo.holdingsWidth ) {
2070      
2071      /* shift position to new playing area, if needed */
2072      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2073        for(i=0; i<BOARD_HEIGHT; i++) 
2074          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2075            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2076              board[i][j];
2077        for(i=0; i<newHeight; i++) {
2078          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2079          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2080        }
2081      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2082        for(i=0; i<BOARD_HEIGHT; i++)
2083          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2084            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2085              board[i][j];
2086      }
2087      gameInfo.boardWidth  = newWidth;
2088      gameInfo.boardHeight = newHeight;
2089      gameInfo.holdingsWidth = newHoldingsWidth;
2090      gameInfo.variant = newVariant;
2091      InitDrawingSizes(-2, 0);
2092    } else gameInfo.variant = newVariant;
2093    CopyBoard(oldBoard, board);   // remember correctly formatted board
2094      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2095    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2096 }
2097
2098 static int loggedOn = FALSE;
2099
2100 /*-- Game start info cache: --*/
2101 int gs_gamenum;
2102 char gs_kind[MSG_SIZ];
2103 static char player1Name[128] = "";
2104 static char player2Name[128] = "";
2105 static char cont_seq[] = "\n\\   ";
2106 static int player1Rating = -1;
2107 static int player2Rating = -1;
2108 /*----------------------------*/
2109
2110 ColorClass curColor = ColorNormal;
2111 int suppressKibitz = 0;
2112
2113 // [HGM] seekgraph
2114 Boolean soughtPending = FALSE;
2115 Boolean seekGraphUp;
2116 #define MAX_SEEK_ADS 200
2117 #define SQUARE 0x80
2118 char *seekAdList[MAX_SEEK_ADS];
2119 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2120 float tcList[MAX_SEEK_ADS];
2121 char colorList[MAX_SEEK_ADS];
2122 int nrOfSeekAds = 0;
2123 int minRating = 1010, maxRating = 2800;
2124 int hMargin = 10, vMargin = 20, h, w;
2125 extern int squareSize, lineGap;
2126
2127 void
2128 PlotSeekAd(int i)
2129 {
2130         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2131         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2132         if(r < minRating+100 && r >=0 ) r = minRating+100;
2133         if(r > maxRating) r = maxRating;
2134         if(tc < 1.) tc = 1.;
2135         if(tc > 95.) tc = 95.;
2136         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2137         y = ((double)r - minRating)/(maxRating - minRating)
2138             * (h-vMargin-squareSize/8-1) + vMargin;
2139         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2140         if(strstr(seekAdList[i], " u ")) color = 1;
2141         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2142            !strstr(seekAdList[i], "bullet") &&
2143            !strstr(seekAdList[i], "blitz") &&
2144            !strstr(seekAdList[i], "standard") ) color = 2;
2145         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2146         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2147 }
2148
2149 void
2150 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2151 {
2152         char buf[MSG_SIZ], *ext = "";
2153         VariantClass v = StringToVariant(type);
2154         if(strstr(type, "wild")) {
2155             ext = type + 4; // append wild number
2156             if(v == VariantFischeRandom) type = "chess960"; else
2157             if(v == VariantLoadable) type = "setup"; else
2158             type = VariantName(v);
2159         }
2160         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2161         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2162             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2163             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2164             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2165             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2166             seekNrList[nrOfSeekAds] = nr;
2167             zList[nrOfSeekAds] = 0;
2168             seekAdList[nrOfSeekAds++] = StrSave(buf);
2169             if(plot) PlotSeekAd(nrOfSeekAds-1);
2170         }
2171 }
2172
2173 void
2174 EraseSeekDot(int i)
2175 {
2176     int x = xList[i], y = yList[i], d=squareSize/4, k;
2177     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2178     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2179     // now replot every dot that overlapped
2180     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2181         int xx = xList[k], yy = yList[k];
2182         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2183             DrawSeekDot(xx, yy, colorList[k]);
2184     }
2185 }
2186
2187 void
2188 RemoveSeekAd(int nr)
2189 {
2190         int i;
2191         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2192             EraseSeekDot(i);
2193             if(seekAdList[i]) free(seekAdList[i]);
2194             seekAdList[i] = seekAdList[--nrOfSeekAds];
2195             seekNrList[i] = seekNrList[nrOfSeekAds];
2196             ratingList[i] = ratingList[nrOfSeekAds];
2197             colorList[i]  = colorList[nrOfSeekAds];
2198             tcList[i] = tcList[nrOfSeekAds];
2199             xList[i]  = xList[nrOfSeekAds];
2200             yList[i]  = yList[nrOfSeekAds];
2201             zList[i]  = zList[nrOfSeekAds];
2202             seekAdList[nrOfSeekAds] = NULL;
2203             break;
2204         }
2205 }
2206
2207 Boolean
2208 MatchSoughtLine(char *line)
2209 {
2210     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2211     int nr, base, inc, u=0; char dummy;
2212
2213     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2214        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2215        (u=1) &&
2216        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2217         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2218         // match: compact and save the line
2219         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2220         return TRUE;
2221     }
2222     return FALSE;
2223 }
2224
2225 int
2226 DrawSeekGraph()
2227 {
2228     int i;
2229     if(!seekGraphUp) return FALSE;
2230     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2231     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2232
2233     DrawSeekBackground(0, 0, w, h);
2234     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2235     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2236     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2237         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2238         yy = h-1-yy;
2239         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2240         if(i%500 == 0) {
2241             char buf[MSG_SIZ];
2242             sprintf(buf, "%d", i);
2243             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2244         }
2245     }
2246     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2247     for(i=1; i<100; i+=(i<10?1:5)) {
2248         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2249         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2250         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2251             char buf[MSG_SIZ];
2252             sprintf(buf, "%d", i);
2253             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2254         }
2255     }
2256     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2257     return TRUE;
2258 }
2259
2260 int SeekGraphClick(ClickType click, int x, int y, int moving)
2261 {
2262     static int lastDown = 0, displayed = 0, lastSecond;
2263     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2264         if(click == Release || moving) return FALSE;
2265         nrOfSeekAds = 0;
2266         soughtPending = TRUE;
2267         SendToICS(ics_prefix);
2268         SendToICS("sought\n"); // should this be "sought all"?
2269     } else { // issue challenge based on clicked ad
2270         int dist = 10000; int i, closest = 0, second = 0;
2271         for(i=0; i<nrOfSeekAds; i++) {
2272             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2273             if(d < dist) { dist = d; closest = i; }
2274             second += (d - zList[i] < 120); // count in-range ads
2275             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2276         }
2277         if(dist < 120) {
2278             char buf[MSG_SIZ];
2279             second = (second > 1);
2280             if(displayed != closest || second != lastSecond) {
2281                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2282                 lastSecond = second; displayed = closest;
2283             }
2284             if(click == Press) {
2285                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2286                 lastDown = closest;
2287                 return TRUE;
2288             } // on press 'hit', only show info
2289             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2290             sprintf(buf, "play %d\n", seekNrList[closest]);
2291             SendToICS(ics_prefix);
2292             SendToICS(buf);
2293             return TRUE; // let incoming board of started game pop down the graph
2294         } else if(click == Release) { // release 'miss' is ignored
2295             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2296             if(moving == 2) { // right up-click
2297                 nrOfSeekAds = 0; // refresh graph
2298                 soughtPending = TRUE;
2299                 SendToICS(ics_prefix);
2300                 SendToICS("sought\n"); // should this be "sought all"?
2301             }
2302             return TRUE;
2303         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2304         // press miss or release hit 'pop down' seek graph
2305         seekGraphUp = FALSE;
2306         DrawPosition(TRUE, NULL);
2307     }
2308     return TRUE;
2309 }
2310
2311 void
2312 read_from_ics(isr, closure, data, count, error)
2313      InputSourceRef isr;
2314      VOIDSTAR closure;
2315      char *data;
2316      int count;
2317      int error;
2318 {
2319 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2320 #define STARTED_NONE 0
2321 #define STARTED_MOVES 1
2322 #define STARTED_BOARD 2
2323 #define STARTED_OBSERVE 3
2324 #define STARTED_HOLDINGS 4
2325 #define STARTED_CHATTER 5
2326 #define STARTED_COMMENT 6
2327 #define STARTED_MOVES_NOHIDE 7
2328     
2329     static int started = STARTED_NONE;
2330     static char parse[20000];
2331     static int parse_pos = 0;
2332     static char buf[BUF_SIZE + 1];
2333     static int firstTime = TRUE, intfSet = FALSE;
2334     static ColorClass prevColor = ColorNormal;
2335     static int savingComment = FALSE;
2336     static int cmatch = 0; // continuation sequence match
2337     char *bp;
2338     char str[500];
2339     int i, oldi;
2340     int buf_len;
2341     int next_out;
2342     int tkind;
2343     int backup;    /* [DM] For zippy color lines */
2344     char *p;
2345     char talker[MSG_SIZ]; // [HGM] chat
2346     int channel;
2347
2348     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2349
2350     if (appData.debugMode) {
2351       if (!error) {
2352         fprintf(debugFP, "<ICS: ");
2353         show_bytes(debugFP, data, count);
2354         fprintf(debugFP, "\n");
2355       }
2356     }
2357
2358     if (appData.debugMode) { int f = forwardMostMove;
2359         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2360                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2361                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2362     }
2363     if (count > 0) {
2364         /* If last read ended with a partial line that we couldn't parse,
2365            prepend it to the new read and try again. */
2366         if (leftover_len > 0) {
2367             for (i=0; i<leftover_len; i++)
2368               buf[i] = buf[leftover_start + i];
2369         }
2370
2371     /* copy new characters into the buffer */
2372     bp = buf + leftover_len;
2373     buf_len=leftover_len;
2374     for (i=0; i<count; i++)
2375     {
2376         // ignore these
2377         if (data[i] == '\r')
2378             continue;
2379
2380         // join lines split by ICS?
2381         if (!appData.noJoin)
2382         {
2383             /*
2384                 Joining just consists of finding matches against the
2385                 continuation sequence, and discarding that sequence
2386                 if found instead of copying it.  So, until a match
2387                 fails, there's nothing to do since it might be the
2388                 complete sequence, and thus, something we don't want
2389                 copied.
2390             */
2391             if (data[i] == cont_seq[cmatch])
2392             {
2393                 cmatch++;
2394                 if (cmatch == strlen(cont_seq))
2395                 {
2396                     cmatch = 0; // complete match.  just reset the counter
2397
2398                     /*
2399                         it's possible for the ICS to not include the space
2400                         at the end of the last word, making our [correct]
2401                         join operation fuse two separate words.  the server
2402                         does this when the space occurs at the width setting.
2403                     */
2404                     if (!buf_len || buf[buf_len-1] != ' ')
2405                     {
2406                         *bp++ = ' ';
2407                         buf_len++;
2408                     }
2409                 }
2410                 continue;
2411             }
2412             else if (cmatch)
2413             {
2414                 /*
2415                     match failed, so we have to copy what matched before
2416                     falling through and copying this character.  In reality,
2417                     this will only ever be just the newline character, but
2418                     it doesn't hurt to be precise.
2419                 */
2420                 strncpy(bp, cont_seq, cmatch);
2421                 bp += cmatch;
2422                 buf_len += cmatch;
2423                 cmatch = 0;
2424             }
2425         }
2426
2427         // copy this char
2428         *bp++ = data[i];
2429         buf_len++;
2430     }
2431
2432         buf[buf_len] = NULLCHAR;
2433 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2434         next_out = 0;
2435         leftover_start = 0;
2436         
2437         i = 0;
2438         while (i < buf_len) {
2439             /* Deal with part of the TELNET option negotiation
2440                protocol.  We refuse to do anything beyond the
2441                defaults, except that we allow the WILL ECHO option,
2442                which ICS uses to turn off password echoing when we are
2443                directly connected to it.  We reject this option
2444                if localLineEditing mode is on (always on in xboard)
2445                and we are talking to port 23, which might be a real
2446                telnet server that will try to keep WILL ECHO on permanently.
2447              */
2448             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2449                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2450                 unsigned char option;
2451                 oldi = i;
2452                 switch ((unsigned char) buf[++i]) {
2453                   case TN_WILL:
2454                     if (appData.debugMode)
2455                       fprintf(debugFP, "\n<WILL ");
2456                     switch (option = (unsigned char) buf[++i]) {
2457                       case TN_ECHO:
2458                         if (appData.debugMode)
2459                           fprintf(debugFP, "ECHO ");
2460                         /* Reply only if this is a change, according
2461                            to the protocol rules. */
2462                         if (remoteEchoOption) break;
2463                         if (appData.localLineEditing &&
2464                             atoi(appData.icsPort) == TN_PORT) {
2465                             TelnetRequest(TN_DONT, TN_ECHO);
2466                         } else {
2467                             EchoOff();
2468                             TelnetRequest(TN_DO, TN_ECHO);
2469                             remoteEchoOption = TRUE;
2470                         }
2471                         break;
2472                       default:
2473                         if (appData.debugMode)
2474                           fprintf(debugFP, "%d ", option);
2475                         /* Whatever this is, we don't want it. */
2476                         TelnetRequest(TN_DONT, option);
2477                         break;
2478                     }
2479                     break;
2480                   case TN_WONT:
2481                     if (appData.debugMode)
2482                       fprintf(debugFP, "\n<WONT ");
2483                     switch (option = (unsigned char) buf[++i]) {
2484                       case TN_ECHO:
2485                         if (appData.debugMode)
2486                           fprintf(debugFP, "ECHO ");
2487                         /* Reply only if this is a change, according
2488                            to the protocol rules. */
2489                         if (!remoteEchoOption) break;
2490                         EchoOn();
2491                         TelnetRequest(TN_DONT, TN_ECHO);
2492                         remoteEchoOption = FALSE;
2493                         break;
2494                       default:
2495                         if (appData.debugMode)
2496                           fprintf(debugFP, "%d ", (unsigned char) option);
2497                         /* Whatever this is, it must already be turned
2498                            off, because we never agree to turn on
2499                            anything non-default, so according to the
2500                            protocol rules, we don't reply. */
2501                         break;
2502                     }
2503                     break;
2504                   case TN_DO:
2505                     if (appData.debugMode)
2506                       fprintf(debugFP, "\n<DO ");
2507                     switch (option = (unsigned char) buf[++i]) {
2508                       default:
2509                         /* Whatever this is, we refuse to do it. */
2510                         if (appData.debugMode)
2511                           fprintf(debugFP, "%d ", option);
2512                         TelnetRequest(TN_WONT, option);
2513                         break;
2514                     }
2515                     break;
2516                   case TN_DONT:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<DONT ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       default:
2521                         if (appData.debugMode)
2522                           fprintf(debugFP, "%d ", option);
2523                         /* Whatever this is, we are already not doing
2524                            it, because we never agree to do anything
2525                            non-default, so according to the protocol
2526                            rules, we don't reply. */
2527                         break;
2528                     }
2529                     break;
2530                   case TN_IAC:
2531                     if (appData.debugMode)
2532                       fprintf(debugFP, "\n<IAC ");
2533                     /* Doubled IAC; pass it through */
2534                     i--;
2535                     break;
2536                   default:
2537                     if (appData.debugMode)
2538                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2539                     /* Drop all other telnet commands on the floor */
2540                     break;
2541                 }
2542                 if (oldi > next_out)
2543                   SendToPlayer(&buf[next_out], oldi - next_out);
2544                 if (++i > next_out)
2545                   next_out = i;
2546                 continue;
2547             }
2548                 
2549             /* OK, this at least will *usually* work */
2550             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2551                 loggedOn = TRUE;
2552             }
2553             
2554             if (loggedOn && !intfSet) {
2555                 if (ics_type == ICS_ICC) {
2556                   sprintf(str,
2557                           "/set-quietly interface %s\n/set-quietly style 12\n",
2558                           programVersion);
2559                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2560                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2561                 } else if (ics_type == ICS_CHESSNET) {
2562                   sprintf(str, "/style 12\n");
2563                 } else {
2564                   strcpy(str, "alias $ @\n$set interface ");
2565                   strcat(str, programVersion);
2566                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2567                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2568                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2569 #ifdef WIN32
2570                   strcat(str, "$iset nohighlight 1\n");
2571 #endif
2572                   strcat(str, "$iset lock 1\n$style 12\n");
2573                 }
2574                 SendToICS(str);
2575                 NotifyFrontendLogin();
2576                 intfSet = TRUE;
2577             }
2578
2579             if (started == STARTED_COMMENT) {
2580                 /* Accumulate characters in comment */
2581                 parse[parse_pos++] = buf[i];
2582                 if (buf[i] == '\n') {
2583                     parse[parse_pos] = NULLCHAR;
2584                     if(chattingPartner>=0) {
2585                         char mess[MSG_SIZ];
2586                         sprintf(mess, "%s%s", talker, parse);
2587                         OutputChatMessage(chattingPartner, mess);
2588                         chattingPartner = -1;
2589                         next_out = i+1; // [HGM] suppress printing in ICS window
2590                     } else
2591                     if(!suppressKibitz) // [HGM] kibitz
2592                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2593                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2594                         int nrDigit = 0, nrAlph = 0, j;
2595                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2596                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2597                         parse[parse_pos] = NULLCHAR;
2598                         // try to be smart: if it does not look like search info, it should go to
2599                         // ICS interaction window after all, not to engine-output window.
2600                         for(j=0; j<parse_pos; j++) { // count letters and digits
2601                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2602                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2603                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2604                         }
2605                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2606                             int depth=0; float score;
2607                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2608                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2609                                 pvInfoList[forwardMostMove-1].depth = depth;
2610                                 pvInfoList[forwardMostMove-1].score = 100*score;
2611                             }
2612                             OutputKibitz(suppressKibitz, parse);
2613                         } else {
2614                             char tmp[MSG_SIZ];
2615                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2616                             SendToPlayer(tmp, strlen(tmp));
2617                         }
2618                         next_out = i+1; // [HGM] suppress printing in ICS window
2619                     }
2620                     started = STARTED_NONE;
2621                 } else {
2622                     /* Don't match patterns against characters in comment */
2623                     i++;
2624                     continue;
2625                 }
2626             }
2627             if (started == STARTED_CHATTER) {
2628                 if (buf[i] != '\n') {
2629                     /* Don't match patterns against characters in chatter */
2630                     i++;
2631                     continue;
2632                 }
2633                 started = STARTED_NONE;
2634                 if(suppressKibitz) next_out = i+1;
2635             }
2636
2637             /* Kludge to deal with rcmd protocol */
2638             if (firstTime && looking_at(buf, &i, "\001*")) {
2639                 DisplayFatalError(&buf[1], 0, 1);
2640                 continue;
2641             } else {
2642                 firstTime = FALSE;
2643             }
2644
2645             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2646                 ics_type = ICS_ICC;
2647                 ics_prefix = "/";
2648                 if (appData.debugMode)
2649                   fprintf(debugFP, "ics_type %d\n", ics_type);
2650                 continue;
2651             }
2652             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2653                 ics_type = ICS_FICS;
2654                 ics_prefix = "$";
2655                 if (appData.debugMode)
2656                   fprintf(debugFP, "ics_type %d\n", ics_type);
2657                 continue;
2658             }
2659             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2660                 ics_type = ICS_CHESSNET;
2661                 ics_prefix = "/";
2662                 if (appData.debugMode)
2663                   fprintf(debugFP, "ics_type %d\n", ics_type);
2664                 continue;
2665             }
2666
2667             if (!loggedOn &&
2668                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2669                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2670                  looking_at(buf, &i, "will be \"*\""))) {
2671               strcpy(ics_handle, star_match[0]);
2672               continue;
2673             }
2674
2675             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2676               char buf[MSG_SIZ];
2677               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2678               DisplayIcsInteractionTitle(buf);
2679               have_set_title = TRUE;
2680             }
2681
2682             /* skip finger notes */
2683             if (started == STARTED_NONE &&
2684                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2685                  (buf[i] == '1' && buf[i+1] == '0')) &&
2686                 buf[i+2] == ':' && buf[i+3] == ' ') {
2687               started = STARTED_CHATTER;
2688               i += 3;
2689               continue;
2690             }
2691
2692             oldi = i;
2693             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2694             if(appData.seekGraph) {
2695                 if(soughtPending && MatchSoughtLine(buf+i)) {
2696                     i = strstr(buf+i, "rated") - buf;
2697                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2698                     next_out = leftover_start = i;
2699                     started = STARTED_CHATTER;
2700                     suppressKibitz = TRUE;
2701                     continue;
2702                 }
2703                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2704                         && looking_at(buf, &i, "* ads displayed")) {
2705                     soughtPending = FALSE;
2706                     seekGraphUp = TRUE;
2707                     DrawSeekGraph();
2708                     continue;
2709                 }
2710                 if(appData.autoRefresh) {
2711                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2712                         int s = (ics_type == ICS_ICC); // ICC format differs
2713                         if(seekGraphUp)
2714                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2715                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2716                         looking_at(buf, &i, "*% "); // eat prompt
2717                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2718                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2719                         next_out = i; // suppress
2720                         continue;
2721                     }
2722                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2723                         char *p = star_match[0];
2724                         while(*p) {
2725                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2726                             while(*p && *p++ != ' '); // next
2727                         }
2728                         looking_at(buf, &i, "*% "); // eat prompt
2729                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                         next_out = i;
2731                         continue;
2732                     }
2733                 }
2734             }
2735
2736             /* skip formula vars */
2737             if (started == STARTED_NONE &&
2738                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2739               started = STARTED_CHATTER;
2740               i += 3;
2741               continue;
2742             }
2743
2744             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2745             if (appData.autoKibitz && started == STARTED_NONE && 
2746                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2747                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2748                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2749                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2750                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2751                         suppressKibitz = TRUE;
2752                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753                         next_out = i;
2754                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2755                                 && (gameMode == IcsPlayingWhite)) ||
2756                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2757                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2758                             started = STARTED_CHATTER; // own kibitz we simply discard
2759                         else {
2760                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2761                             parse_pos = 0; parse[0] = NULLCHAR;
2762                             savingComment = TRUE;
2763                             suppressKibitz = gameMode != IcsObserving ? 2 :
2764                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2765                         } 
2766                         continue;
2767                 } else
2768                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2769                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2770                          && atoi(star_match[0])) {
2771                     // suppress the acknowledgements of our own autoKibitz
2772                     char *p;
2773                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2774                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2775                     SendToPlayer(star_match[0], strlen(star_match[0]));
2776                     if(looking_at(buf, &i, "*% ")) // eat prompt
2777                         suppressKibitz = FALSE;
2778                     next_out = i;
2779                     continue;
2780                 }
2781             } // [HGM] kibitz: end of patch
2782
2783             // [HGM] chat: intercept tells by users for which we have an open chat window
2784             channel = -1;
2785             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2786                                            looking_at(buf, &i, "* whispers:") ||
2787                                            looking_at(buf, &i, "* kibitzes:") ||
2788                                            looking_at(buf, &i, "* shouts:") ||
2789                                            looking_at(buf, &i, "* c-shouts:") ||
2790                                            looking_at(buf, &i, "--> * ") ||
2791                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2792                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2793                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2794                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2795                 int p;
2796                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2797                 chattingPartner = -1;
2798
2799                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2800                 for(p=0; p<MAX_CHAT; p++) {
2801                     if(channel == atoi(chatPartner[p])) {
2802                     talker[0] = '['; strcat(talker, "] ");
2803                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2804                     chattingPartner = p; break;
2805                     }
2806                 } else
2807                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2808                 for(p=0; p<MAX_CHAT; p++) {
2809                     if(!strcmp("kibitzes", chatPartner[p])) {
2810                         talker[0] = '['; strcat(talker, "] ");
2811                         chattingPartner = p; break;
2812                     }
2813                 } else
2814                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2815                 for(p=0; p<MAX_CHAT; p++) {
2816                     if(!strcmp("whispers", chatPartner[p])) {
2817                         talker[0] = '['; strcat(talker, "] ");
2818                         chattingPartner = p; break;
2819                     }
2820                 } else
2821                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2822                   if(buf[i-8] == '-' && buf[i-3] == 't')
2823                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2824                     if(!strcmp("c-shouts", chatPartner[p])) {
2825                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2826                         chattingPartner = p; break;
2827                     }
2828                   }
2829                   if(chattingPartner < 0)
2830                   for(p=0; p<MAX_CHAT; p++) {
2831                     if(!strcmp("shouts", chatPartner[p])) {
2832                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2833                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2834                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2835                         chattingPartner = p; break;
2836                     }
2837                   }
2838                 }
2839                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2840                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2841                     talker[0] = 0; Colorize(ColorTell, FALSE);
2842                     chattingPartner = p; break;
2843                 }
2844                 if(chattingPartner<0) i = oldi; else {
2845                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2846                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2847                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2848                     started = STARTED_COMMENT;
2849                     parse_pos = 0; parse[0] = NULLCHAR;
2850                     savingComment = 3 + chattingPartner; // counts as TRUE
2851                     suppressKibitz = TRUE;
2852                     continue;
2853                 }
2854             } // [HGM] chat: end of patch
2855
2856             if (appData.zippyTalk || appData.zippyPlay) {
2857                 /* [DM] Backup address for color zippy lines */
2858                 backup = i;
2859 #if ZIPPY
2860                if (loggedOn == TRUE)
2861                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2862                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2863 #endif
2864             } // [DM] 'else { ' deleted
2865                 if (
2866                     /* Regular tells and says */
2867                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2868                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2869                     looking_at(buf, &i, "* says: ") ||
2870                     /* Don't color "message" or "messages" output */
2871                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2872                     looking_at(buf, &i, "*. * at *:*: ") ||
2873                     looking_at(buf, &i, "--* (*:*): ") ||
2874                     /* Message notifications (same color as tells) */
2875                     looking_at(buf, &i, "* has left a message ") ||
2876                     looking_at(buf, &i, "* just sent you a message:\n") ||
2877                     /* Whispers and kibitzes */
2878                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2879                     looking_at(buf, &i, "* kibitzes: ") ||
2880                     /* Channel tells */
2881                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2882
2883                   if (tkind == 1 && strchr(star_match[0], ':')) {
2884                       /* Avoid "tells you:" spoofs in channels */
2885                      tkind = 3;
2886                   }
2887                   if (star_match[0][0] == NULLCHAR ||
2888                       strchr(star_match[0], ' ') ||
2889                       (tkind == 3 && strchr(star_match[1], ' '))) {
2890                     /* Reject bogus matches */
2891                     i = oldi;
2892                   } else {
2893                     if (appData.colorize) {
2894                       if (oldi > next_out) {
2895                         SendToPlayer(&buf[next_out], oldi - next_out);
2896                         next_out = oldi;
2897                       }
2898                       switch (tkind) {
2899                       case 1:
2900                         Colorize(ColorTell, FALSE);
2901                         curColor = ColorTell;
2902                         break;
2903                       case 2:
2904                         Colorize(ColorKibitz, FALSE);
2905                         curColor = ColorKibitz;
2906                         break;
2907                       case 3:
2908                         p = strrchr(star_match[1], '(');
2909                         if (p == NULL) {
2910                           p = star_match[1];
2911                         } else {
2912                           p++;
2913                         }
2914                         if (atoi(p) == 1) {
2915                           Colorize(ColorChannel1, FALSE);
2916                           curColor = ColorChannel1;
2917                         } else {
2918                           Colorize(ColorChannel, FALSE);
2919                           curColor = ColorChannel;
2920                         }
2921                         break;
2922                       case 5:
2923                         curColor = ColorNormal;
2924                         break;
2925                       }
2926                     }
2927                     if (started == STARTED_NONE && appData.autoComment &&
2928                         (gameMode == IcsObserving ||
2929                          gameMode == IcsPlayingWhite ||
2930                          gameMode == IcsPlayingBlack)) {
2931                       parse_pos = i - oldi;
2932                       memcpy(parse, &buf[oldi], parse_pos);
2933                       parse[parse_pos] = NULLCHAR;
2934                       started = STARTED_COMMENT;
2935                       savingComment = TRUE;
2936                     } else {
2937                       started = STARTED_CHATTER;
2938                       savingComment = FALSE;
2939                     }
2940                     loggedOn = TRUE;
2941                     continue;
2942                   }
2943                 }
2944
2945                 if (looking_at(buf, &i, "* s-shouts: ") ||
2946                     looking_at(buf, &i, "* c-shouts: ")) {
2947                     if (appData.colorize) {
2948                         if (oldi > next_out) {
2949                             SendToPlayer(&buf[next_out], oldi - next_out);
2950                             next_out = oldi;
2951                         }
2952                         Colorize(ColorSShout, FALSE);
2953                         curColor = ColorSShout;
2954                     }
2955                     loggedOn = TRUE;
2956                     started = STARTED_CHATTER;
2957                     continue;
2958                 }
2959
2960                 if (looking_at(buf, &i, "--->")) {
2961                     loggedOn = TRUE;
2962                     continue;
2963                 }
2964
2965                 if (looking_at(buf, &i, "* shouts: ") ||
2966                     looking_at(buf, &i, "--> ")) {
2967                     if (appData.colorize) {
2968                         if (oldi > next_out) {
2969                             SendToPlayer(&buf[next_out], oldi - next_out);
2970                             next_out = oldi;
2971                         }
2972                         Colorize(ColorShout, FALSE);
2973                         curColor = ColorShout;
2974                     }
2975                     loggedOn = TRUE;
2976                     started = STARTED_CHATTER;
2977                     continue;
2978                 }
2979
2980                 if (looking_at( buf, &i, "Challenge:")) {
2981                     if (appData.colorize) {
2982                         if (oldi > next_out) {
2983                             SendToPlayer(&buf[next_out], oldi - next_out);
2984                             next_out = oldi;
2985                         }
2986                         Colorize(ColorChallenge, FALSE);
2987                         curColor = ColorChallenge;
2988                     }
2989                     loggedOn = TRUE;
2990                     continue;
2991                 }
2992
2993                 if (looking_at(buf, &i, "* offers you") ||
2994                     looking_at(buf, &i, "* offers to be") ||
2995                     looking_at(buf, &i, "* would like to") ||
2996                     looking_at(buf, &i, "* requests to") ||
2997                     looking_at(buf, &i, "Your opponent offers") ||
2998                     looking_at(buf, &i, "Your opponent requests")) {
2999
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorRequest, FALSE);
3006                         curColor = ColorRequest;
3007                     }
3008                     continue;
3009                 }
3010
3011                 if (looking_at(buf, &i, "* (*) seeking")) {
3012                     if (appData.colorize) {
3013                         if (oldi > next_out) {
3014                             SendToPlayer(&buf[next_out], oldi - next_out);
3015                             next_out = oldi;
3016                         }
3017                         Colorize(ColorSeek, FALSE);
3018                         curColor = ColorSeek;
3019                     }
3020                     continue;
3021             }
3022
3023             if (looking_at(buf, &i, "\\   ")) {
3024                 if (prevColor != ColorNormal) {
3025                     if (oldi > next_out) {
3026                         SendToPlayer(&buf[next_out], oldi - next_out);
3027                         next_out = oldi;
3028                     }
3029                     Colorize(prevColor, TRUE);
3030                     curColor = prevColor;
3031                 }
3032                 if (savingComment) {
3033                     parse_pos = i - oldi;
3034                     memcpy(parse, &buf[oldi], parse_pos);
3035                     parse[parse_pos] = NULLCHAR;
3036                     started = STARTED_COMMENT;
3037                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3038                         chattingPartner = savingComment - 3; // kludge to remember the box
3039                 } else {
3040                     started = STARTED_CHATTER;
3041                 }
3042                 continue;
3043             }
3044
3045             if (looking_at(buf, &i, "Black Strength :") ||
3046                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3047                 looking_at(buf, &i, "<10>") ||
3048                 looking_at(buf, &i, "#@#")) {
3049                 /* Wrong board style */
3050                 loggedOn = TRUE;
3051                 SendToICS(ics_prefix);
3052                 SendToICS("set style 12\n");
3053                 SendToICS(ics_prefix);
3054                 SendToICS("refresh\n");
3055                 continue;
3056             }
3057             
3058             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3059                 ICSInitScript();
3060                 have_sent_ICS_logon = 1;
3061                 continue;
3062             }
3063               
3064             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3065                 (looking_at(buf, &i, "\n<12> ") ||
3066                  looking_at(buf, &i, "<12> "))) {
3067                 loggedOn = TRUE;
3068                 if (oldi > next_out) {
3069                     SendToPlayer(&buf[next_out], oldi - next_out);
3070                 }
3071                 next_out = i;
3072                 started = STARTED_BOARD;
3073                 parse_pos = 0;
3074                 continue;
3075             }
3076
3077             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3078                 looking_at(buf, &i, "<b1> ")) {
3079                 if (oldi > next_out) {
3080                     SendToPlayer(&buf[next_out], oldi - next_out);
3081                 }
3082                 next_out = i;
3083                 started = STARTED_HOLDINGS;
3084                 parse_pos = 0;
3085                 continue;
3086             }
3087
3088             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3089                 loggedOn = TRUE;
3090                 /* Header for a move list -- first line */
3091
3092                 switch (ics_getting_history) {
3093                   case H_FALSE:
3094                     switch (gameMode) {
3095                       case IcsIdle:
3096                       case BeginningOfGame:
3097                         /* User typed "moves" or "oldmoves" while we
3098                            were idle.  Pretend we asked for these
3099                            moves and soak them up so user can step
3100                            through them and/or save them.
3101                            */
3102                         Reset(FALSE, TRUE);
3103                         gameMode = IcsObserving;
3104                         ModeHighlight();
3105                         ics_gamenum = -1;
3106                         ics_getting_history = H_GOT_UNREQ_HEADER;
3107                         break;
3108                       case EditGame: /*?*/
3109                       case EditPosition: /*?*/
3110                         /* Should above feature work in these modes too? */
3111                         /* For now it doesn't */
3112                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3113                         break;
3114                       default:
3115                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3116                         break;
3117                     }
3118                     break;
3119                   case H_REQUESTED:
3120                     /* Is this the right one? */
3121                     if (gameInfo.white && gameInfo.black &&
3122                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3123                         strcmp(gameInfo.black, star_match[2]) == 0) {
3124                         /* All is well */
3125                         ics_getting_history = H_GOT_REQ_HEADER;
3126                     }
3127                     break;
3128                   case H_GOT_REQ_HEADER:
3129                   case H_GOT_UNREQ_HEADER:
3130                   case H_GOT_UNWANTED_HEADER:
3131                   case H_GETTING_MOVES:
3132                     /* Should not happen */
3133                     DisplayError(_("Error gathering move list: two headers"), 0);
3134                     ics_getting_history = H_FALSE;
3135                     break;
3136                 }
3137
3138                 /* Save player ratings into gameInfo if needed */
3139                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3140                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3141                     (gameInfo.whiteRating == -1 ||
3142                      gameInfo.blackRating == -1)) {
3143
3144                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3145                     gameInfo.blackRating = string_to_rating(star_match[3]);
3146                     if (appData.debugMode)
3147                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3148                               gameInfo.whiteRating, gameInfo.blackRating);
3149                 }
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i,
3154               "* * match, initial time: * minute*, increment: * second")) {
3155                 /* Header for a move list -- second line */
3156                 /* Initial board will follow if this is a wild game */
3157                 if (gameInfo.event != NULL) free(gameInfo.event);
3158                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3159                 gameInfo.event = StrSave(str);
3160                 /* [HGM] we switched variant. Translate boards if needed. */
3161                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3162                 continue;
3163             }
3164
3165             if (looking_at(buf, &i, "Move  ")) {
3166                 /* Beginning of a move list */
3167                 switch (ics_getting_history) {
3168                   case H_FALSE:
3169                     /* Normally should not happen */
3170                     /* Maybe user hit reset while we were parsing */
3171                     break;
3172                   case H_REQUESTED:
3173                     /* Happens if we are ignoring a move list that is not
3174                      * the one we just requested.  Common if the user
3175                      * tries to observe two games without turning off
3176                      * getMoveList */
3177                     break;
3178                   case H_GETTING_MOVES:
3179                     /* Should not happen */
3180                     DisplayError(_("Error gathering move list: nested"), 0);
3181                     ics_getting_history = H_FALSE;
3182                     break;
3183                   case H_GOT_REQ_HEADER:
3184                     ics_getting_history = H_GETTING_MOVES;
3185                     started = STARTED_MOVES;
3186                     parse_pos = 0;
3187                     if (oldi > next_out) {
3188                         SendToPlayer(&buf[next_out], oldi - next_out);
3189                     }
3190                     break;
3191                   case H_GOT_UNREQ_HEADER:
3192                     ics_getting_history = H_GETTING_MOVES;
3193                     started = STARTED_MOVES_NOHIDE;
3194                     parse_pos = 0;
3195                     break;
3196                   case H_GOT_UNWANTED_HEADER:
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200                 continue;
3201             }                           
3202             
3203             if (looking_at(buf, &i, "% ") ||
3204                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3205                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3206                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3207                     soughtPending = FALSE;
3208                     seekGraphUp = TRUE;
3209                     DrawSeekGraph();
3210                 }
3211                 if(suppressKibitz) next_out = i;
3212                 savingComment = FALSE;
3213                 suppressKibitz = 0;
3214                 switch (started) {
3215                   case STARTED_MOVES:
3216                   case STARTED_MOVES_NOHIDE:
3217                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3218                     parse[parse_pos + i - oldi] = NULLCHAR;
3219                     ParseGameHistory(parse);
3220 #if ZIPPY
3221                     if (appData.zippyPlay && first.initDone) {
3222                         FeedMovesToProgram(&first, forwardMostMove);
3223                         if (gameMode == IcsPlayingWhite) {
3224                             if (WhiteOnMove(forwardMostMove)) {
3225                                 if (first.sendTime) {
3226                                   if (first.useColors) {
3227                                     SendToProgram("black\n", &first); 
3228                                   }
3229                                   SendTimeRemaining(&first, TRUE);
3230                                 }
3231                                 if (first.useColors) {
3232                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3233                                 }
3234                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3235                                 first.maybeThinking = TRUE;
3236                             } else {
3237                                 if (first.usePlayother) {
3238                                   if (first.sendTime) {
3239                                     SendTimeRemaining(&first, TRUE);
3240                                   }
3241                                   SendToProgram("playother\n", &first);
3242                                   firstMove = FALSE;
3243                                 } else {
3244                                   firstMove = TRUE;
3245                                 }
3246                             }
3247                         } else if (gameMode == IcsPlayingBlack) {
3248                             if (!WhiteOnMove(forwardMostMove)) {
3249                                 if (first.sendTime) {
3250                                   if (first.useColors) {
3251                                     SendToProgram("white\n", &first);
3252                                   }
3253                                   SendTimeRemaining(&first, FALSE);
3254                                 }
3255                                 if (first.useColors) {
3256                                   SendToProgram("black\n", &first);
3257                                 }
3258                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3259                                 first.maybeThinking = TRUE;
3260                             } else {
3261                                 if (first.usePlayother) {
3262                                   if (first.sendTime) {
3263                                     SendTimeRemaining(&first, FALSE);
3264                                   }
3265                                   SendToProgram("playother\n", &first);
3266                                   firstMove = FALSE;
3267                                 } else {
3268                                   firstMove = TRUE;
3269                                 }
3270                             }
3271                         }                       
3272                     }
3273 #endif
3274                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3275                         /* Moves came from oldmoves or moves command
3276                            while we weren't doing anything else.
3277                            */
3278                         currentMove = forwardMostMove;
3279                         ClearHighlights();/*!!could figure this out*/
3280                         flipView = appData.flipView;
3281                         DrawPosition(TRUE, boards[currentMove]);
3282                         DisplayBothClocks();
3283                         sprintf(str, "%s vs. %s",
3284                                 gameInfo.white, gameInfo.black);
3285                         DisplayTitle(str);
3286                         gameMode = IcsIdle;
3287                     } else {
3288                         /* Moves were history of an active game */
3289                         if (gameInfo.resultDetails != NULL) {
3290                             free(gameInfo.resultDetails);
3291                             gameInfo.resultDetails = NULL;
3292                         }
3293                     }
3294                     HistorySet(parseList, backwardMostMove,
3295                                forwardMostMove, currentMove-1);
3296                     DisplayMove(currentMove - 1);
3297                     if (started == STARTED_MOVES) next_out = i;
3298                     started = STARTED_NONE;
3299                     ics_getting_history = H_FALSE;
3300                     break;
3301
3302                   case STARTED_OBSERVE:
3303                     started = STARTED_NONE;
3304                     SendToICS(ics_prefix);
3305                     SendToICS("refresh\n");
3306                     break;
3307
3308                   default:
3309                     break;
3310                 }
3311                 if(bookHit) { // [HGM] book: simulate book reply
3312                     static char bookMove[MSG_SIZ]; // a bit generous?
3313
3314                     programStats.nodes = programStats.depth = programStats.time = 
3315                     programStats.score = programStats.got_only_move = 0;
3316                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3317
3318                     strcpy(bookMove, "move ");
3319                     strcat(bookMove, bookHit);
3320                     HandleMachineMove(bookMove, &first);
3321                 }
3322                 continue;
3323             }
3324             
3325             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3326                  started == STARTED_HOLDINGS ||
3327                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3328                 /* Accumulate characters in move list or board */
3329                 parse[parse_pos++] = buf[i];
3330             }
3331             
3332             /* Start of game messages.  Mostly we detect start of game
3333                when the first board image arrives.  On some versions
3334                of the ICS, though, we need to do a "refresh" after starting
3335                to observe in order to get the current board right away. */
3336             if (looking_at(buf, &i, "Adding game * to observation list")) {
3337                 started = STARTED_OBSERVE;
3338                 continue;
3339             }
3340
3341             /* Handle auto-observe */
3342             if (appData.autoObserve &&
3343                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3344                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3345                 char *player;
3346                 /* Choose the player that was highlighted, if any. */
3347                 if (star_match[0][0] == '\033' ||
3348                     star_match[1][0] != '\033') {
3349                     player = star_match[0];
3350                 } else {
3351                     player = star_match[2];
3352                 }
3353                 sprintf(str, "%sobserve %s\n",
3354                         ics_prefix, StripHighlightAndTitle(player));
3355                 SendToICS(str);
3356
3357                 /* Save ratings from notify string */
3358                 strcpy(player1Name, star_match[0]);
3359                 player1Rating = string_to_rating(star_match[1]);
3360                 strcpy(player2Name, star_match[2]);
3361                 player2Rating = string_to_rating(star_match[3]);
3362
3363                 if (appData.debugMode)
3364                   fprintf(debugFP, 
3365                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3366                           player1Name, player1Rating,
3367                           player2Name, player2Rating);
3368
3369                 continue;
3370             }
3371
3372             /* Deal with automatic examine mode after a game,
3373                and with IcsObserving -> IcsExamining transition */
3374             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3375                 looking_at(buf, &i, "has made you an examiner of game *")) {
3376
3377                 int gamenum = atoi(star_match[0]);
3378                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3379                     gamenum == ics_gamenum) {
3380                     /* We were already playing or observing this game;
3381                        no need to refetch history */
3382                     gameMode = IcsExamining;
3383                     if (pausing) {
3384                         pauseExamForwardMostMove = forwardMostMove;
3385                     } else if (currentMove < forwardMostMove) {
3386                         ForwardInner(forwardMostMove);
3387                     }
3388                 } else {
3389                     /* I don't think this case really can happen */
3390                     SendToICS(ics_prefix);
3391                     SendToICS("refresh\n");
3392                 }
3393                 continue;
3394             }    
3395             
3396             /* Error messages */
3397 //          if (ics_user_moved) {
3398             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3399                 if (looking_at(buf, &i, "Illegal move") ||
3400                     looking_at(buf, &i, "Not a legal move") ||
3401                     looking_at(buf, &i, "Your king is in check") ||
3402                     looking_at(buf, &i, "It isn't your turn") ||
3403                     looking_at(buf, &i, "It is not your move")) {
3404                     /* Illegal move */
3405                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3406                         currentMove = forwardMostMove-1;
3407                         DisplayMove(currentMove - 1); /* before DMError */
3408                         DrawPosition(FALSE, boards[currentMove]);
3409                         SwitchClocks(forwardMostMove-1); // [HGM] race
3410                         DisplayBothClocks();
3411                     }
3412                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3413                     ics_user_moved = 0;
3414                     continue;
3415                 }
3416             }
3417
3418             if (looking_at(buf, &i, "still have time") ||
3419                 looking_at(buf, &i, "not out of time") ||
3420                 looking_at(buf, &i, "either player is out of time") ||
3421                 looking_at(buf, &i, "has timeseal; checking")) {
3422                 /* We must have called his flag a little too soon */
3423                 whiteFlag = blackFlag = FALSE;
3424                 continue;
3425             }
3426
3427             if (looking_at(buf, &i, "added * seconds to") ||
3428                 looking_at(buf, &i, "seconds were added to")) {
3429                 /* Update the clocks */
3430                 SendToICS(ics_prefix);
3431                 SendToICS("refresh\n");
3432                 continue;
3433             }
3434
3435             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3436                 ics_clock_paused = TRUE;
3437                 StopClocks();
3438                 continue;
3439             }
3440
3441             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3442                 ics_clock_paused = FALSE;
3443                 StartClocks();
3444                 continue;
3445             }
3446
3447             /* Grab player ratings from the Creating: message.
3448                Note we have to check for the special case when
3449                the ICS inserts things like [white] or [black]. */
3450             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3451                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3452                 /* star_matches:
3453                    0    player 1 name (not necessarily white)
3454                    1    player 1 rating
3455                    2    empty, white, or black (IGNORED)
3456                    3    player 2 name (not necessarily black)
3457                    4    player 2 rating
3458                    
3459                    The names/ratings are sorted out when the game
3460                    actually starts (below).
3461                 */
3462                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3463                 player1Rating = string_to_rating(star_match[1]);
3464                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3465                 player2Rating = string_to_rating(star_match[4]);
3466
3467                 if (appData.debugMode)
3468                   fprintf(debugFP, 
3469                           "Ratings from 'Creating:' %s %d, %s %d\n",
3470                           player1Name, player1Rating,
3471                           player2Name, player2Rating);
3472
3473                 continue;
3474             }
3475             
3476             /* Improved generic start/end-of-game messages */
3477             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3478                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3479                 /* If tkind == 0: */
3480                 /* star_match[0] is the game number */
3481                 /*           [1] is the white player's name */
3482                 /*           [2] is the black player's name */
3483                 /* For end-of-game: */
3484                 /*           [3] is the reason for the game end */
3485                 /*           [4] is a PGN end game-token, preceded by " " */
3486                 /* For start-of-game: */
3487                 /*           [3] begins with "Creating" or "Continuing" */
3488                 /*           [4] is " *" or empty (don't care). */
3489                 int gamenum = atoi(star_match[0]);
3490                 char *whitename, *blackname, *why, *endtoken;
3491                 ChessMove endtype = (ChessMove) 0;
3492
3493                 if (tkind == 0) {
3494                   whitename = star_match[1];
3495                   blackname = star_match[2];
3496                   why = star_match[3];
3497                   endtoken = star_match[4];
3498                 } else {
3499                   whitename = star_match[1];
3500                   blackname = star_match[3];
3501                   why = star_match[5];
3502                   endtoken = star_match[6];
3503                 }
3504
3505                 /* Game start messages */
3506                 if (strncmp(why, "Creating ", 9) == 0 ||
3507                     strncmp(why, "Continuing ", 11) == 0) {
3508                     gs_gamenum = gamenum;
3509                     strcpy(gs_kind, strchr(why, ' ') + 1);
3510                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3511 #if ZIPPY
3512                     if (appData.zippyPlay) {
3513                         ZippyGameStart(whitename, blackname);
3514                     }
3515 #endif /*ZIPPY*/
3516                     partnerBoardValid = FALSE; // [HGM] bughouse
3517                     continue;
3518                 }
3519
3520                 /* Game end messages */
3521                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3522                     ics_gamenum != gamenum) {
3523                     continue;
3524                 }
3525                 while (endtoken[0] == ' ') endtoken++;
3526                 switch (endtoken[0]) {
3527                   case '*':
3528                   default:
3529                     endtype = GameUnfinished;
3530                     break;
3531                   case '0':
3532                     endtype = BlackWins;
3533                     break;
3534                   case '1':
3535                     if (endtoken[1] == '/')
3536                       endtype = GameIsDrawn;
3537                     else
3538                       endtype = WhiteWins;
3539                     break;
3540                 }
3541                 GameEnds(endtype, why, GE_ICS);
3542 #if ZIPPY
3543                 if (appData.zippyPlay && first.initDone) {
3544                     ZippyGameEnd(endtype, why);
3545                     if (first.pr == NULL) {
3546                       /* Start the next process early so that we'll
3547                          be ready for the next challenge */
3548                       StartChessProgram(&first);
3549                     }
3550                     /* Send "new" early, in case this command takes
3551                        a long time to finish, so that we'll be ready
3552                        for the next challenge. */
3553                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3554                     Reset(TRUE, TRUE);
3555                 }
3556 #endif /*ZIPPY*/
3557                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3558                 continue;
3559             }
3560
3561             if (looking_at(buf, &i, "Removing game * from observation") ||
3562                 looking_at(buf, &i, "no longer observing game *") ||
3563                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3564                 if (gameMode == IcsObserving &&
3565                     atoi(star_match[0]) == ics_gamenum)
3566                   {
3567                       /* icsEngineAnalyze */
3568                       if (appData.icsEngineAnalyze) {
3569                             ExitAnalyzeMode();
3570                             ModeHighlight();
3571                       }
3572                       StopClocks();
3573                       gameMode = IcsIdle;
3574                       ics_gamenum = -1;
3575                       ics_user_moved = FALSE;
3576                   }
3577                 continue;
3578             }
3579
3580             if (looking_at(buf, &i, "no longer examining game *")) {
3581                 if (gameMode == IcsExamining &&
3582                     atoi(star_match[0]) == ics_gamenum)
3583                   {
3584                       gameMode = IcsIdle;
3585                       ics_gamenum = -1;
3586                       ics_user_moved = FALSE;
3587                   }
3588                 continue;
3589             }
3590
3591             /* Advance leftover_start past any newlines we find,
3592                so only partial lines can get reparsed */
3593             if (looking_at(buf, &i, "\n")) {
3594                 prevColor = curColor;
3595                 if (curColor != ColorNormal) {
3596                     if (oldi > next_out) {
3597                         SendToPlayer(&buf[next_out], oldi - next_out);
3598                         next_out = oldi;
3599                     }
3600                     Colorize(ColorNormal, FALSE);
3601                     curColor = ColorNormal;
3602                 }
3603                 if (started == STARTED_BOARD) {
3604                     started = STARTED_NONE;
3605                     parse[parse_pos] = NULLCHAR;
3606                     ParseBoard12(parse);
3607                     ics_user_moved = 0;
3608
3609                     /* Send premove here */
3610                     if (appData.premove) {
3611                       char str[MSG_SIZ];
3612                       if (currentMove == 0 &&
3613                           gameMode == IcsPlayingWhite &&
3614                           appData.premoveWhite) {
3615                         sprintf(str, "%s\n", appData.premoveWhiteText);
3616                         if (appData.debugMode)
3617                           fprintf(debugFP, "Sending premove:\n");
3618                         SendToICS(str);
3619                       } else if (currentMove == 1 &&
3620                                  gameMode == IcsPlayingBlack &&
3621                                  appData.premoveBlack) {
3622                         sprintf(str, "%s\n", appData.premoveBlackText);
3623                         if (appData.debugMode)
3624                           fprintf(debugFP, "Sending premove:\n");
3625                         SendToICS(str);
3626                       } else if (gotPremove) {
3627                         gotPremove = 0;
3628                         ClearPremoveHighlights();
3629                         if (appData.debugMode)
3630                           fprintf(debugFP, "Sending premove:\n");
3631                           UserMoveEvent(premoveFromX, premoveFromY, 
3632                                         premoveToX, premoveToY, 
3633                                         premovePromoChar);
3634                       }
3635                     }
3636
3637                     /* Usually suppress following prompt */
3638                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3639                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3640                         if (looking_at(buf, &i, "*% ")) {
3641                             savingComment = FALSE;
3642                             suppressKibitz = 0;
3643                         }
3644                     }
3645                     next_out = i;
3646                 } else if (started == STARTED_HOLDINGS) {
3647                     int gamenum;
3648                     char new_piece[MSG_SIZ];
3649                     started = STARTED_NONE;
3650                     parse[parse_pos] = NULLCHAR;
3651                     if (appData.debugMode)
3652                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3653                                                         parse, currentMove);
3654                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3655                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3656                         if (gameInfo.variant == VariantNormal) {
3657                           /* [HGM] We seem to switch variant during a game!
3658                            * Presumably no holdings were displayed, so we have
3659                            * to move the position two files to the right to
3660                            * create room for them!
3661                            */
3662                           VariantClass newVariant;
3663                           switch(gameInfo.boardWidth) { // base guess on board width
3664                                 case 9:  newVariant = VariantShogi; break;
3665                                 case 10: newVariant = VariantGreat; break;
3666                                 default: newVariant = VariantCrazyhouse; break;
3667                           }
3668                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3669                           /* Get a move list just to see the header, which
3670                              will tell us whether this is really bug or zh */
3671                           if (ics_getting_history == H_FALSE) {
3672                             ics_getting_history = H_REQUESTED;
3673                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3674                             SendToICS(str);
3675                           }
3676                         }
3677                         new_piece[0] = NULLCHAR;
3678                         sscanf(parse, "game %d white [%s black [%s <- %s",
3679                                &gamenum, white_holding, black_holding,
3680                                new_piece);
3681                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3682                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3683                         /* [HGM] copy holdings to board holdings area */
3684                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3685                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3686                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3687 #if ZIPPY
3688                         if (appData.zippyPlay && first.initDone) {
3689                             ZippyHoldings(white_holding, black_holding,
3690                                           new_piece);
3691                         }
3692 #endif /*ZIPPY*/
3693                         if (tinyLayout || smallLayout) {
3694                             char wh[16], bh[16];
3695                             PackHolding(wh, white_holding);
3696                             PackHolding(bh, black_holding);
3697                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3698                                     gameInfo.white, gameInfo.black);
3699                         } else {
3700                             sprintf(str, "%s [%s] vs. %s [%s]",
3701                                     gameInfo.white, white_holding,
3702                                     gameInfo.black, black_holding);
3703                         }
3704                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3705                         DrawPosition(FALSE, boards[currentMove]);
3706                         DisplayTitle(str);
3707                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3708                         sscanf(parse, "game %d white [%s black [%s <- %s",
3709                                &gamenum, white_holding, black_holding,
3710                                new_piece);
3711                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3712                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3713                         /* [HGM] copy holdings to partner-board holdings area */
3714                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3715                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3716                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3717                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3718                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3719                       }
3720                     }
3721                     /* Suppress following prompt */
3722                     if (looking_at(buf, &i, "*% ")) {
3723                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3724                         savingComment = FALSE;
3725                         suppressKibitz = 0;
3726                     }
3727                     next_out = i;
3728                 }
3729                 continue;
3730             }
3731
3732             i++;                /* skip unparsed character and loop back */
3733         }
3734         
3735         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3736 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3737 //          SendToPlayer(&buf[next_out], i - next_out);
3738             started != STARTED_HOLDINGS && leftover_start > next_out) {
3739             SendToPlayer(&buf[next_out], leftover_start - next_out);
3740             next_out = i;
3741         }
3742         
3743         leftover_len = buf_len - leftover_start;
3744         /* if buffer ends with something we couldn't parse,
3745            reparse it after appending the next read */
3746         
3747     } else if (count == 0) {
3748         RemoveInputSource(isr);
3749         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3750     } else {
3751         DisplayFatalError(_("Error reading from ICS"), error, 1);
3752     }
3753 }
3754
3755
3756 /* Board style 12 looks like this:
3757    
3758    <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
3759    
3760  * The "<12> " is stripped before it gets to this routine.  The two
3761  * trailing 0's (flip state and clock ticking) are later addition, and
3762  * some chess servers may not have them, or may have only the first.
3763  * Additional trailing fields may be added in the future.  
3764  */
3765
3766 #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"
3767
3768 #define RELATION_OBSERVING_PLAYED    0
3769 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3770 #define RELATION_PLAYING_MYMOVE      1
3771 #define RELATION_PLAYING_NOTMYMOVE  -1
3772 #define RELATION_EXAMINING           2
3773 #define RELATION_ISOLATED_BOARD     -3
3774 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3775
3776 void
3777 ParseBoard12(string)
3778      char *string;
3779
3780     GameMode newGameMode;
3781     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3782     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3783     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3784     char to_play, board_chars[200];
3785     char move_str[500], str[500], elapsed_time[500];
3786     char black[32], white[32];
3787     Board board;
3788     int prevMove = currentMove;
3789     int ticking = 2;
3790     ChessMove moveType;
3791     int fromX, fromY, toX, toY;
3792     char promoChar;
3793     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3794     char *bookHit = NULL; // [HGM] book
3795     Boolean weird = FALSE, reqFlag = FALSE;
3796
3797     fromX = fromY = toX = toY = -1;
3798     
3799     newGame = FALSE;
3800
3801     if (appData.debugMode)
3802       fprintf(debugFP, _("Parsing board: %s\n"), string);
3803
3804     move_str[0] = NULLCHAR;
3805     elapsed_time[0] = NULLCHAR;
3806     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3807         int  i = 0, j;
3808         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3809             if(string[i] == ' ') { ranks++; files = 0; }
3810             else files++;
3811             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3812             i++;
3813         }
3814         for(j = 0; j <i; j++) board_chars[j] = string[j];
3815         board_chars[i] = '\0';
3816         string += i + 1;
3817     }
3818     n = sscanf(string, PATTERN, &to_play, &double_push,
3819                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3820                &gamenum, white, black, &relation, &basetime, &increment,
3821                &white_stren, &black_stren, &white_time, &black_time,
3822                &moveNum, str, elapsed_time, move_str, &ics_flip,
3823                &ticking);
3824
3825     if (n < 21) {
3826         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3827         DisplayError(str, 0);
3828         return;
3829     }
3830
3831     /* Convert the move number to internal form */
3832     moveNum = (moveNum - 1) * 2;
3833     if (to_play == 'B') moveNum++;
3834     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3835       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3836                         0, 1);
3837       return;
3838     }
3839     
3840     switch (relation) {
3841       case RELATION_OBSERVING_PLAYED:
3842       case RELATION_OBSERVING_STATIC:
3843         if (gamenum == -1) {
3844             /* Old ICC buglet */
3845             relation = RELATION_OBSERVING_STATIC;
3846         }
3847         newGameMode = IcsObserving;
3848         break;
3849       case RELATION_PLAYING_MYMOVE:
3850       case RELATION_PLAYING_NOTMYMOVE:
3851         newGameMode =
3852           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3853             IcsPlayingWhite : IcsPlayingBlack;
3854         break;
3855       case RELATION_EXAMINING:
3856         newGameMode = IcsExamining;
3857         break;
3858       case RELATION_ISOLATED_BOARD:
3859       default:
3860         /* Just display this board.  If user was doing something else,
3861            we will forget about it until the next board comes. */ 
3862         newGameMode = IcsIdle;
3863         break;
3864       case RELATION_STARTING_POSITION:
3865         newGameMode = gameMode;
3866         break;
3867     }
3868     
3869     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3870          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3871       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3872       char *toSqr;
3873       for (k = 0; k < ranks; k++) {
3874         for (j = 0; j < files; j++)
3875           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3876         if(gameInfo.holdingsWidth > 1) {
3877              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3878              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3879         }
3880       }
3881       CopyBoard(partnerBoard, board);
3882       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3883         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3884         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3885       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3886       if(toSqr = strchr(str, '-')) {
3887         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3888         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3889       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3890       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3891       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3892       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3893       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3894       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3895                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3896       DisplayMessage(partnerStatus, "");
3897         partnerBoardValid = TRUE;
3898       return;
3899     }
3900
3901     /* Modify behavior for initial board display on move listing
3902        of wild games.
3903        */
3904     switch (ics_getting_history) {
3905       case H_FALSE:
3906       case H_REQUESTED:
3907         break;
3908       case H_GOT_REQ_HEADER:
3909       case H_GOT_UNREQ_HEADER:
3910         /* This is the initial position of the current game */
3911         gamenum = ics_gamenum;
3912         moveNum = 0;            /* old ICS bug workaround */
3913         if (to_play == 'B') {
3914           startedFromSetupPosition = TRUE;
3915           blackPlaysFirst = TRUE;
3916           moveNum = 1;
3917           if (forwardMostMove == 0) forwardMostMove = 1;
3918           if (backwardMostMove == 0) backwardMostMove = 1;
3919           if (currentMove == 0) currentMove = 1;
3920         }
3921         newGameMode = gameMode;
3922         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3923         break;
3924       case H_GOT_UNWANTED_HEADER:
3925         /* This is an initial board that we don't want */
3926         return;
3927       case H_GETTING_MOVES:
3928         /* Should not happen */
3929         DisplayError(_("Error gathering move list: extra board"), 0);
3930         ics_getting_history = H_FALSE;
3931         return;
3932     }
3933
3934    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3935                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3936      /* [HGM] We seem to have switched variant unexpectedly
3937       * Try to guess new variant from board size
3938       */
3939           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3940           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3941           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3942           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3943           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3944           if(!weird) newVariant = VariantNormal;
3945           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3946           /* Get a move list just to see the header, which
3947              will tell us whether this is really bug or zh */
3948           if (ics_getting_history == H_FALSE) {
3949             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3950             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3951             SendToICS(str);
3952           }
3953     }
3954     
3955     /* Take action if this is the first board of a new game, or of a
3956        different game than is currently being displayed.  */
3957     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3958         relation == RELATION_ISOLATED_BOARD) {
3959         
3960         /* Forget the old game and get the history (if any) of the new one */
3961         if (gameMode != BeginningOfGame) {
3962           Reset(TRUE, TRUE);
3963         }
3964         newGame = TRUE;
3965         if (appData.autoRaiseBoard) BoardToTop();
3966         prevMove = -3;
3967         if (gamenum == -1) {
3968             newGameMode = IcsIdle;
3969         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3970                    appData.getMoveList && !reqFlag) {
3971             /* Need to get game history */
3972             ics_getting_history = H_REQUESTED;
3973             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3974             SendToICS(str);
3975         }
3976         
3977         /* Initially flip the board to have black on the bottom if playing
3978            black or if the ICS flip flag is set, but let the user change
3979            it with the Flip View button. */
3980         flipView = appData.autoFlipView ? 
3981           (newGameMode == IcsPlayingBlack) || ics_flip :
3982           appData.flipView;
3983         
3984         /* Done with values from previous mode; copy in new ones */
3985         gameMode = newGameMode;
3986         ModeHighlight();
3987         ics_gamenum = gamenum;
3988         if (gamenum == gs_gamenum) {
3989             int klen = strlen(gs_kind);
3990             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3991             sprintf(str, "ICS %s", gs_kind);
3992             gameInfo.event = StrSave(str);
3993         } else {
3994             gameInfo.event = StrSave("ICS game");
3995         }
3996         gameInfo.site = StrSave(appData.icsHost);
3997         gameInfo.date = PGNDate();
3998         gameInfo.round = StrSave("-");
3999         gameInfo.white = StrSave(white);
4000         gameInfo.black = StrSave(black);
4001         timeControl = basetime * 60 * 1000;
4002         timeControl_2 = 0;
4003         timeIncrement = increment * 1000;
4004         movesPerSession = 0;
4005         gameInfo.timeControl = TimeControlTagValue();
4006         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4007   if (appData.debugMode) {
4008     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4009     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4010     setbuf(debugFP, NULL);
4011   }
4012
4013         gameInfo.outOfBook = NULL;
4014         
4015         /* Do we have the ratings? */
4016         if (strcmp(player1Name, white) == 0 &&
4017             strcmp(player2Name, black) == 0) {
4018             if (appData.debugMode)
4019               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4020                       player1Rating, player2Rating);
4021             gameInfo.whiteRating = player1Rating;
4022             gameInfo.blackRating = player2Rating;
4023         } else if (strcmp(player2Name, white) == 0 &&
4024                    strcmp(player1Name, black) == 0) {
4025             if (appData.debugMode)
4026               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4027                       player2Rating, player1Rating);
4028             gameInfo.whiteRating = player2Rating;
4029             gameInfo.blackRating = player1Rating;
4030         }
4031         player1Name[0] = player2Name[0] = NULLCHAR;
4032
4033         /* Silence shouts if requested */
4034         if (appData.quietPlay &&
4035             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4036             SendToICS(ics_prefix);
4037             SendToICS("set shout 0\n");
4038         }
4039     }
4040     
4041     /* Deal with midgame name changes */
4042     if (!newGame) {
4043         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4044             if (gameInfo.white) free(gameInfo.white);
4045             gameInfo.white = StrSave(white);
4046         }
4047         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4048             if (gameInfo.black) free(gameInfo.black);
4049             gameInfo.black = StrSave(black);
4050         }
4051     }
4052     
4053     /* Throw away game result if anything actually changes in examine mode */
4054     if (gameMode == IcsExamining && !newGame) {
4055         gameInfo.result = GameUnfinished;
4056         if (gameInfo.resultDetails != NULL) {
4057             free(gameInfo.resultDetails);
4058             gameInfo.resultDetails = NULL;
4059         }
4060     }
4061     
4062     /* In pausing && IcsExamining mode, we ignore boards coming
4063        in if they are in a different variation than we are. */
4064     if (pauseExamInvalid) return;
4065     if (pausing && gameMode == IcsExamining) {
4066         if (moveNum <= pauseExamForwardMostMove) {
4067             pauseExamInvalid = TRUE;
4068             forwardMostMove = pauseExamForwardMostMove;
4069             return;
4070         }
4071     }
4072     
4073   if (appData.debugMode) {
4074     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4075   }
4076     /* Parse the board */
4077     for (k = 0; k < ranks; k++) {
4078       for (j = 0; j < files; j++)
4079         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4080       if(gameInfo.holdingsWidth > 1) {
4081            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4082            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4083       }
4084     }
4085     CopyBoard(boards[moveNum], board);
4086     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4087     if (moveNum == 0) {
4088         startedFromSetupPosition =
4089           !CompareBoards(board, initialPosition);
4090         if(startedFromSetupPosition)
4091             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4092     }
4093
4094     /* [HGM] Set castling rights. Take the outermost Rooks,
4095        to make it also work for FRC opening positions. Note that board12
4096        is really defective for later FRC positions, as it has no way to
4097        indicate which Rook can castle if they are on the same side of King.
4098        For the initial position we grant rights to the outermost Rooks,
4099        and remember thos rights, and we then copy them on positions
4100        later in an FRC game. This means WB might not recognize castlings with
4101        Rooks that have moved back to their original position as illegal,
4102        but in ICS mode that is not its job anyway.
4103     */
4104     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4105     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4106
4107         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4108             if(board[0][i] == WhiteRook) j = i;
4109         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4110         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4111             if(board[0][i] == WhiteRook) j = i;
4112         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4113         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4114             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4115         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4116         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4117             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4118         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4119
4120         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4121         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4122             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4123         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4124             if(board[BOARD_HEIGHT-1][k] == bKing)
4125                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4126         if(gameInfo.variant == VariantTwoKings) {
4127             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4128             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4129             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4130         }
4131     } else { int r;
4132         r = boards[moveNum][CASTLING][0] = initialRights[0];
4133         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4134         r = boards[moveNum][CASTLING][1] = initialRights[1];
4135         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4136         r = boards[moveNum][CASTLING][3] = initialRights[3];
4137         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4138         r = boards[moveNum][CASTLING][4] = initialRights[4];
4139         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4140         /* wildcastle kludge: always assume King has rights */
4141         r = boards[moveNum][CASTLING][2] = initialRights[2];
4142         r = boards[moveNum][CASTLING][5] = initialRights[5];
4143     }
4144     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4145     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4146
4147     
4148     if (ics_getting_history == H_GOT_REQ_HEADER ||
4149         ics_getting_history == H_GOT_UNREQ_HEADER) {
4150         /* This was an initial position from a move list, not
4151            the current position */
4152         return;
4153     }
4154     
4155     /* Update currentMove and known move number limits */
4156     newMove = newGame || moveNum > forwardMostMove;
4157
4158     if (newGame) {
4159         forwardMostMove = backwardMostMove = currentMove = moveNum;
4160         if (gameMode == IcsExamining && moveNum == 0) {
4161           /* Workaround for ICS limitation: we are not told the wild
4162              type when starting to examine a game.  But if we ask for
4163              the move list, the move list header will tell us */
4164             ics_getting_history = H_REQUESTED;
4165             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4166             SendToICS(str);
4167         }
4168     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4169                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4170 #if ZIPPY
4171         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4172         /* [HGM] applied this also to an engine that is silently watching        */
4173         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4174             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4175             gameInfo.variant == currentlyInitializedVariant) {
4176           takeback = forwardMostMove - moveNum;
4177           for (i = 0; i < takeback; i++) {
4178             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4179             SendToProgram("undo\n", &first);
4180           }
4181         }
4182 #endif
4183
4184         forwardMostMove = moveNum;
4185         if (!pausing || currentMove > forwardMostMove)
4186           currentMove = forwardMostMove;
4187     } else {
4188         /* New part of history that is not contiguous with old part */ 
4189         if (pausing && gameMode == IcsExamining) {
4190             pauseExamInvalid = TRUE;
4191             forwardMostMove = pauseExamForwardMostMove;
4192             return;
4193         }
4194         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4195 #if ZIPPY
4196             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4197                 // [HGM] when we will receive the move list we now request, it will be
4198                 // fed to the engine from the first move on. So if the engine is not
4199                 // in the initial position now, bring it there.
4200                 InitChessProgram(&first, 0);
4201             }
4202 #endif
4203             ics_getting_history = H_REQUESTED;
4204             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4205             SendToICS(str);
4206         }
4207         forwardMostMove = backwardMostMove = currentMove = moveNum;
4208     }
4209     
4210     /* Update the clocks */
4211     if (strchr(elapsed_time, '.')) {
4212       /* Time is in ms */
4213       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4214       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4215     } else {
4216       /* Time is in seconds */
4217       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4218       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4219     }
4220       
4221
4222 #if ZIPPY
4223     if (appData.zippyPlay && newGame &&
4224         gameMode != IcsObserving && gameMode != IcsIdle &&
4225         gameMode != IcsExamining)
4226       ZippyFirstBoard(moveNum, basetime, increment);
4227 #endif
4228     
4229     /* Put the move on the move list, first converting
4230        to canonical algebraic form. */
4231     if (moveNum > 0) {
4232   if (appData.debugMode) {
4233     if (appData.debugMode) { int f = forwardMostMove;
4234         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4235                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4236                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4237     }
4238     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4239     fprintf(debugFP, "moveNum = %d\n", moveNum);
4240     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4241     setbuf(debugFP, NULL);
4242   }
4243         if (moveNum <= backwardMostMove) {
4244             /* We don't know what the board looked like before
4245                this move.  Punt. */
4246             strcpy(parseList[moveNum - 1], move_str);
4247             strcat(parseList[moveNum - 1], " ");
4248             strcat(parseList[moveNum - 1], elapsed_time);
4249             moveList[moveNum - 1][0] = NULLCHAR;
4250         } else if (strcmp(move_str, "none") == 0) {
4251             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4252             /* Again, we don't know what the board looked like;
4253                this is really the start of the game. */
4254             parseList[moveNum - 1][0] = NULLCHAR;
4255             moveList[moveNum - 1][0] = NULLCHAR;
4256             backwardMostMove = moveNum;
4257             startedFromSetupPosition = TRUE;
4258             fromX = fromY = toX = toY = -1;
4259         } else {
4260           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4261           //                 So we parse the long-algebraic move string in stead of the SAN move
4262           int valid; char buf[MSG_SIZ], *prom;
4263
4264           // str looks something like "Q/a1-a2"; kill the slash
4265           if(str[1] == '/') 
4266                 sprintf(buf, "%c%s", str[0], str+2);
4267           else  strcpy(buf, str); // might be castling
4268           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4269                 strcat(buf, prom); // long move lacks promo specification!
4270           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4271                 if(appData.debugMode) 
4272                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4273                 strcpy(move_str, buf);
4274           }
4275           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4276                                 &fromX, &fromY, &toX, &toY, &promoChar)
4277                || ParseOneMove(buf, moveNum - 1, &moveType,
4278                                 &fromX, &fromY, &toX, &toY, &promoChar);
4279           // end of long SAN patch
4280           if (valid) {
4281             (void) CoordsToAlgebraic(boards[moveNum - 1],
4282                                      PosFlags(moveNum - 1),
4283                                      fromY, fromX, toY, toX, promoChar,
4284                                      parseList[moveNum-1]);
4285             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4286               case MT_NONE:
4287               case MT_STALEMATE:
4288               default:
4289                 break;
4290               case MT_CHECK:
4291                 if(gameInfo.variant != VariantShogi)
4292                     strcat(parseList[moveNum - 1], "+");
4293                 break;
4294               case MT_CHECKMATE:
4295               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4296                 strcat(parseList[moveNum - 1], "#");
4297                 break;
4298             }
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             /* currentMoveString is set as a side-effect of ParseOneMove */
4302             strcpy(moveList[moveNum - 1], currentMoveString);
4303             strcat(moveList[moveNum - 1], "\n");
4304           } else {
4305             /* Move from ICS was illegal!?  Punt. */
4306   if (appData.debugMode) {
4307     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4308     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4309   }
4310             strcpy(parseList[moveNum - 1], move_str);
4311             strcat(parseList[moveNum - 1], " ");
4312             strcat(parseList[moveNum - 1], elapsed_time);
4313             moveList[moveNum - 1][0] = NULLCHAR;
4314             fromX = fromY = toX = toY = -1;
4315           }
4316         }
4317   if (appData.debugMode) {
4318     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4319     setbuf(debugFP, NULL);
4320   }
4321
4322 #if ZIPPY
4323         /* Send move to chess program (BEFORE animating it). */
4324         if (appData.zippyPlay && !newGame && newMove && 
4325            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4326
4327             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4328                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4329                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4330                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4331                             move_str);
4332                     DisplayError(str, 0);
4333                 } else {
4334                     if (first.sendTime) {
4335                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4336                     }
4337                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4338                     if (firstMove && !bookHit) {
4339                         firstMove = FALSE;
4340                         if (first.useColors) {
4341                           SendToProgram(gameMode == IcsPlayingWhite ?
4342                                         "white\ngo\n" :
4343                                         "black\ngo\n", &first);
4344                         } else {
4345                           SendToProgram("go\n", &first);
4346                         }
4347                         first.maybeThinking = TRUE;
4348                     }
4349                 }
4350             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4351               if (moveList[moveNum - 1][0] == NULLCHAR) {
4352                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4353                 DisplayError(str, 0);
4354               } else {
4355                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4356                 SendMoveToProgram(moveNum - 1, &first);
4357               }
4358             }
4359         }
4360 #endif
4361     }
4362
4363     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4364         /* If move comes from a remote source, animate it.  If it
4365            isn't remote, it will have already been animated. */
4366         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4367             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4368         }
4369         if (!pausing && appData.highlightLastMove) {
4370             SetHighlights(fromX, fromY, toX, toY);
4371         }
4372     }
4373     
4374     /* Start the clocks */
4375     whiteFlag = blackFlag = FALSE;
4376     appData.clockMode = !(basetime == 0 && increment == 0);
4377     if (ticking == 0) {
4378       ics_clock_paused = TRUE;
4379       StopClocks();
4380     } else if (ticking == 1) {
4381       ics_clock_paused = FALSE;
4382     }
4383     if (gameMode == IcsIdle ||
4384         relation == RELATION_OBSERVING_STATIC ||
4385         relation == RELATION_EXAMINING ||
4386         ics_clock_paused)
4387       DisplayBothClocks();
4388     else
4389       StartClocks();
4390     
4391     /* Display opponents and material strengths */
4392     if (gameInfo.variant != VariantBughouse &&
4393         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4394         if (tinyLayout || smallLayout) {
4395             if(gameInfo.variant == VariantNormal)
4396                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4397                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4398                     basetime, increment);
4399             else
4400                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4401                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4402                     basetime, increment, (int) gameInfo.variant);
4403         } else {
4404             if(gameInfo.variant == VariantNormal)
4405                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4406                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4407                     basetime, increment);
4408             else
4409                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4410                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4411                     basetime, increment, VariantName(gameInfo.variant));
4412         }
4413         DisplayTitle(str);
4414   if (appData.debugMode) {
4415     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4416   }
4417     }
4418
4419
4420     /* Display the board */
4421     if (!pausing && !appData.noGUI) {
4422       
4423       if (appData.premove)
4424           if (!gotPremove || 
4425              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4426              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4427               ClearPremoveHighlights();
4428
4429       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4430         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4431       DrawPosition(j, boards[currentMove]);
4432
4433       DisplayMove(moveNum - 1);
4434       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4435             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4436               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4437         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4438       }
4439     }
4440
4441     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4442 #if ZIPPY
4443     if(bookHit) { // [HGM] book: simulate book reply
4444         static char bookMove[MSG_SIZ]; // a bit generous?
4445
4446         programStats.nodes = programStats.depth = programStats.time = 
4447         programStats.score = programStats.got_only_move = 0;
4448         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4449
4450         strcpy(bookMove, "move ");
4451         strcat(bookMove, bookHit);
4452         HandleMachineMove(bookMove, &first);
4453     }
4454 #endif
4455 }
4456
4457 void
4458 GetMoveListEvent()
4459 {
4460     char buf[MSG_SIZ];
4461     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4462         ics_getting_history = H_REQUESTED;
4463         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4464         SendToICS(buf);
4465     }
4466 }
4467
4468 void
4469 AnalysisPeriodicEvent(force)
4470      int force;
4471 {
4472     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4473          && !force) || !appData.periodicUpdates)
4474       return;
4475
4476     /* Send . command to Crafty to collect stats */
4477     SendToProgram(".\n", &first);
4478
4479     /* Don't send another until we get a response (this makes
4480        us stop sending to old Crafty's which don't understand
4481        the "." command (sending illegal cmds resets node count & time,
4482        which looks bad)) */
4483     programStats.ok_to_send = 0;
4484 }
4485
4486 void ics_update_width(new_width)
4487         int new_width;
4488 {
4489         ics_printf("set width %d\n", new_width);
4490 }
4491
4492 void
4493 SendMoveToProgram(moveNum, cps)
4494      int moveNum;
4495      ChessProgramState *cps;
4496 {
4497     char buf[MSG_SIZ];
4498
4499     if (cps->useUsermove) {
4500       SendToProgram("usermove ", cps);
4501     }
4502     if (cps->useSAN) {
4503       char *space;
4504       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4505         int len = space - parseList[moveNum];
4506         memcpy(buf, parseList[moveNum], len);
4507         buf[len++] = '\n';
4508         buf[len] = NULLCHAR;
4509       } else {
4510         sprintf(buf, "%s\n", parseList[moveNum]);
4511       }
4512       SendToProgram(buf, cps);
4513     } else {
4514       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4515         AlphaRank(moveList[moveNum], 4);
4516         SendToProgram(moveList[moveNum], cps);
4517         AlphaRank(moveList[moveNum], 4); // and back
4518       } else
4519       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4520        * the engine. It would be nice to have a better way to identify castle 
4521        * moves here. */
4522       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4523                                                                          && cps->useOOCastle) {
4524         int fromX = moveList[moveNum][0] - AAA; 
4525         int fromY = moveList[moveNum][1] - ONE;
4526         int toX = moveList[moveNum][2] - AAA; 
4527         int toY = moveList[moveNum][3] - ONE;
4528         if((boards[moveNum][fromY][fromX] == WhiteKing 
4529             && boards[moveNum][toY][toX] == WhiteRook)
4530            || (boards[moveNum][fromY][fromX] == BlackKing 
4531                && boards[moveNum][toY][toX] == BlackRook)) {
4532           if(toX > fromX) SendToProgram("O-O\n", cps);
4533           else SendToProgram("O-O-O\n", cps);
4534         }
4535         else SendToProgram(moveList[moveNum], cps);
4536       }
4537       else SendToProgram(moveList[moveNum], cps);
4538       /* End of additions by Tord */
4539     }
4540
4541     /* [HGM] setting up the opening has brought engine in force mode! */
4542     /*       Send 'go' if we are in a mode where machine should play. */
4543     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4544         (gameMode == TwoMachinesPlay   ||
4545 #if ZIPPY
4546          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4547 #endif
4548          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4549         SendToProgram("go\n", cps);
4550   if (appData.debugMode) {
4551     fprintf(debugFP, "(extra)\n");
4552   }
4553     }
4554     setboardSpoiledMachineBlack = 0;
4555 }
4556
4557 void
4558 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4559      ChessMove moveType;
4560      int fromX, fromY, toX, toY;
4561      char promoChar;
4562 {
4563     char user_move[MSG_SIZ];
4564
4565     switch (moveType) {
4566       default:
4567         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4568                 (int)moveType, fromX, fromY, toX, toY);
4569         DisplayError(user_move + strlen("say "), 0);
4570         break;
4571       case WhiteKingSideCastle:
4572       case BlackKingSideCastle:
4573       case WhiteQueenSideCastleWild:
4574       case BlackQueenSideCastleWild:
4575       /* PUSH Fabien */
4576       case WhiteHSideCastleFR:
4577       case BlackHSideCastleFR:
4578       /* POP Fabien */
4579         sprintf(user_move, "o-o\n");
4580         break;
4581       case WhiteQueenSideCastle:
4582       case BlackQueenSideCastle:
4583       case WhiteKingSideCastleWild:
4584       case BlackKingSideCastleWild:
4585       /* PUSH Fabien */
4586       case WhiteASideCastleFR:
4587       case BlackASideCastleFR:
4588       /* POP Fabien */
4589         sprintf(user_move, "o-o-o\n");
4590         break;
4591       case WhiteNonPromotion:
4592       case BlackNonPromotion:
4593         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4594         break;
4595       case WhitePromotion:
4596       case BlackPromotion:
4597         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4598             sprintf(user_move, "%c%c%c%c=%c\n",
4599                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4600                 PieceToChar(WhiteFerz));
4601         else if(gameInfo.variant == VariantGreat)
4602             sprintf(user_move, "%c%c%c%c=%c\n",
4603                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4604                 PieceToChar(WhiteMan));
4605         else
4606             sprintf(user_move, "%c%c%c%c=%c\n",
4607                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4608                 promoChar);
4609         break;
4610       case WhiteDrop:
4611       case BlackDrop:
4612         sprintf(user_move, "%c@%c%c\n",
4613                 ToUpper(PieceToChar((ChessSquare) fromX)),
4614                 AAA + toX, ONE + toY);
4615         break;
4616       case NormalMove:
4617       case WhiteCapturesEnPassant:
4618       case BlackCapturesEnPassant:
4619       case IllegalMove:  /* could be a variant we don't quite understand */
4620         sprintf(user_move, "%c%c%c%c\n",
4621                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4622         break;
4623     }
4624     SendToICS(user_move);
4625     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4626         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4627 }
4628
4629 void
4630 UploadGameEvent()
4631 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4632     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4633     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4634     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4635         DisplayError("You cannot do this while you are playing or observing", 0);
4636         return;
4637     }
4638     if(gameMode != IcsExamining) { // is this ever not the case?
4639         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4640
4641         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4642             sprintf(command, "match %s", ics_handle);
4643         } else { // on FICS we must first go to general examine mode
4644             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4645         }
4646         if(gameInfo.variant != VariantNormal) {
4647             // try figure out wild number, as xboard names are not always valid on ICS
4648             for(i=1; i<=36; i++) {
4649                 sprintf(buf, "wild/%d", i);
4650                 if(StringToVariant(buf) == gameInfo.variant) break;
4651             }
4652             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4653             else if(i == 22) sprintf(buf, "%s fr\n", command);
4654             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4655         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4656         SendToICS(ics_prefix);
4657         SendToICS(buf);
4658         if(startedFromSetupPosition || backwardMostMove != 0) {
4659           fen = PositionToFEN(backwardMostMove, NULL);
4660           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4661             sprintf(buf, "loadfen %s\n", fen);
4662             SendToICS(buf);
4663           } else { // FICS: everything has to set by separate bsetup commands
4664             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4665             sprintf(buf, "bsetup fen %s\n", fen);
4666             SendToICS(buf);
4667             if(!WhiteOnMove(backwardMostMove)) {
4668                 SendToICS("bsetup tomove black\n");
4669             }
4670             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4671             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4672             SendToICS(buf);
4673             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4674             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4675             SendToICS(buf);
4676             i = boards[backwardMostMove][EP_STATUS];
4677             if(i >= 0) { // set e.p.
4678                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4679                 SendToICS(buf);
4680             }
4681             bsetup++;
4682           }
4683         }
4684       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4685             SendToICS("bsetup done\n"); // switch to normal examining.
4686     }
4687     for(i = backwardMostMove; i<last; i++) {
4688         char buf[20];
4689         sprintf(buf, "%s\n", parseList[i]);
4690         SendToICS(buf);
4691     }
4692     SendToICS(ics_prefix);
4693     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4694 }
4695
4696 void
4697 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4698      int rf, ff, rt, ft;
4699      char promoChar;
4700      char move[7];
4701 {
4702     if (rf == DROP_RANK) {
4703         sprintf(move, "%c@%c%c\n",
4704                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4705     } else {
4706         if (promoChar == 'x' || promoChar == NULLCHAR) {
4707             sprintf(move, "%c%c%c%c\n",
4708                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4709         } else {
4710             sprintf(move, "%c%c%c%c%c\n",
4711                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4712         }
4713     }
4714 }
4715
4716 void
4717 ProcessICSInitScript(f)
4718      FILE *f;
4719 {
4720     char buf[MSG_SIZ];
4721
4722     while (fgets(buf, MSG_SIZ, f)) {
4723         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4724     }
4725
4726     fclose(f);
4727 }
4728
4729
4730 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4731 void
4732 AlphaRank(char *move, int n)
4733 {
4734 //    char *p = move, c; int x, y;
4735
4736     if (appData.debugMode) {
4737         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4738     }
4739
4740     if(move[1]=='*' && 
4741        move[2]>='0' && move[2]<='9' &&
4742        move[3]>='a' && move[3]<='x'    ) {
4743         move[1] = '@';
4744         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4745         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4746     } else
4747     if(move[0]>='0' && move[0]<='9' &&
4748        move[1]>='a' && move[1]<='x' &&
4749        move[2]>='0' && move[2]<='9' &&
4750        move[3]>='a' && move[3]<='x'    ) {
4751         /* input move, Shogi -> normal */
4752         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4753         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4754         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4755         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4756     } else
4757     if(move[1]=='@' &&
4758        move[3]>='0' && move[3]<='9' &&
4759        move[2]>='a' && move[2]<='x'    ) {
4760         move[1] = '*';
4761         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4762         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4763     } else
4764     if(
4765        move[0]>='a' && move[0]<='x' &&
4766        move[3]>='0' && move[3]<='9' &&
4767        move[2]>='a' && move[2]<='x'    ) {
4768          /* output move, normal -> Shogi */
4769         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4770         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4771         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4772         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4773         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4774     }
4775     if (appData.debugMode) {
4776         fprintf(debugFP, "   out = '%s'\n", move);
4777     }
4778 }
4779
4780 char yy_textstr[8000];
4781
4782 /* Parser for moves from gnuchess, ICS, or user typein box */
4783 Boolean
4784 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4785      char *move;
4786      int moveNum;
4787      ChessMove *moveType;
4788      int *fromX, *fromY, *toX, *toY;
4789      char *promoChar;
4790 {       
4791     if (appData.debugMode) {
4792         fprintf(debugFP, "move to parse: %s\n", move);
4793     }
4794     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4795
4796     switch (*moveType) {
4797       case WhitePromotion:
4798       case BlackPromotion:
4799       case WhiteNonPromotion:
4800       case BlackNonPromotion:
4801       case NormalMove:
4802       case WhiteCapturesEnPassant:
4803       case BlackCapturesEnPassant:
4804       case WhiteKingSideCastle:
4805       case WhiteQueenSideCastle:
4806       case BlackKingSideCastle:
4807       case BlackQueenSideCastle:
4808       case WhiteKingSideCastleWild:
4809       case WhiteQueenSideCastleWild:
4810       case BlackKingSideCastleWild:
4811       case BlackQueenSideCastleWild:
4812       /* Code added by Tord: */
4813       case WhiteHSideCastleFR:
4814       case WhiteASideCastleFR:
4815       case BlackHSideCastleFR:
4816       case BlackASideCastleFR:
4817       /* End of code added by Tord */
4818       case IllegalMove:         /* bug or odd chess variant */
4819         *fromX = currentMoveString[0] - AAA;
4820         *fromY = currentMoveString[1] - ONE;
4821         *toX = currentMoveString[2] - AAA;
4822         *toY = currentMoveString[3] - ONE;
4823         *promoChar = currentMoveString[4];
4824         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4825             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4826     if (appData.debugMode) {
4827         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4828     }
4829             *fromX = *fromY = *toX = *toY = 0;
4830             return FALSE;
4831         }
4832         if (appData.testLegality) {
4833           return (*moveType != IllegalMove);
4834         } else {
4835           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4836                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4837         }
4838
4839       case WhiteDrop:
4840       case BlackDrop:
4841         *fromX = *moveType == WhiteDrop ?
4842           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4843           (int) CharToPiece(ToLower(currentMoveString[0]));
4844         *fromY = DROP_RANK;
4845         *toX = currentMoveString[2] - AAA;
4846         *toY = currentMoveString[3] - ONE;
4847         *promoChar = NULLCHAR;
4848         return TRUE;
4849
4850       case AmbiguousMove:
4851       case ImpossibleMove:
4852       case (ChessMove) 0:       /* end of file */
4853       case ElapsedTime:
4854       case Comment:
4855       case PGNTag:
4856       case NAG:
4857       case WhiteWins:
4858       case BlackWins:
4859       case GameIsDrawn:
4860       default:
4861     if (appData.debugMode) {
4862         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4863     }
4864         /* bug? */
4865         *fromX = *fromY = *toX = *toY = 0;
4866         *promoChar = NULLCHAR;
4867         return FALSE;
4868     }
4869 }
4870
4871
4872 void
4873 ParsePV(char *pv, Boolean storeComments)
4874 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4875   int fromX, fromY, toX, toY; char promoChar;
4876   ChessMove moveType;
4877   Boolean valid;
4878   int nr = 0;
4879
4880   endPV = forwardMostMove;
4881   do {
4882     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4883     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4884     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4885 if(appData.debugMode){
4886 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);
4887 }
4888     if(!valid && nr == 0 &&
4889        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4890         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4891         // Hande case where played move is different from leading PV move
4892         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4893         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4894         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4895         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4896           endPV += 2; // if position different, keep this
4897           moveList[endPV-1][0] = fromX + AAA;
4898           moveList[endPV-1][1] = fromY + ONE;
4899           moveList[endPV-1][2] = toX + AAA;
4900           moveList[endPV-1][3] = toY + ONE;
4901           parseList[endPV-1][0] = NULLCHAR;
4902           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4903         }
4904       }
4905     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4906     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4907     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4908     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4909         valid++; // allow comments in PV
4910         continue;
4911     }
4912     nr++;
4913     if(endPV+1 > framePtr) break; // no space, truncate
4914     if(!valid) break;
4915     endPV++;
4916     CopyBoard(boards[endPV], boards[endPV-1]);
4917     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4918     moveList[endPV-1][0] = fromX + AAA;
4919     moveList[endPV-1][1] = fromY + ONE;
4920     moveList[endPV-1][2] = toX + AAA;
4921     moveList[endPV-1][3] = toY + ONE;
4922     if(storeComments)
4923         CoordsToAlgebraic(boards[endPV - 1],
4924                              PosFlags(endPV - 1),
4925                              fromY, fromX, toY, toX, promoChar,
4926                              parseList[endPV - 1]);
4927     else
4928         parseList[endPV-1][0] = NULLCHAR;
4929   } while(valid);
4930   currentMove = endPV;
4931   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4932   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4933                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4934   DrawPosition(TRUE, boards[currentMove]);
4935 }
4936
4937 static int lastX, lastY;
4938
4939 Boolean
4940 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4941 {
4942         int startPV;
4943         char *p;
4944
4945         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4946         lastX = x; lastY = y;
4947         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4948         startPV = index;
4949         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4950         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4951         index = startPV;
4952         do{ while(buf[index] && buf[index] != '\n') index++;
4953         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4954         buf[index] = 0;
4955         ParsePV(buf+startPV, FALSE);
4956         *start = startPV; *end = index-1;
4957         return TRUE;
4958 }
4959
4960 Boolean
4961 LoadPV(int x, int y)
4962 { // called on right mouse click to load PV
4963   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4964   lastX = x; lastY = y;
4965   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4966   return TRUE;
4967 }
4968
4969 void
4970 UnLoadPV()
4971 {
4972   if(endPV < 0) return;
4973   endPV = -1;
4974   currentMove = forwardMostMove;
4975   ClearPremoveHighlights();
4976   DrawPosition(TRUE, boards[currentMove]);
4977 }
4978
4979 void
4980 MovePV(int x, int y, int h)
4981 { // step through PV based on mouse coordinates (called on mouse move)
4982   int margin = h>>3, step = 0;
4983
4984   if(endPV < 0) return;
4985   // we must somehow check if right button is still down (might be released off board!)
4986   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4987   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4988   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4989   if(!step) return;
4990   lastX = x; lastY = y;
4991   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4992   currentMove += step;
4993   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4994   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4995                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4996   DrawPosition(FALSE, boards[currentMove]);
4997 }
4998
4999
5000 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5001 // All positions will have equal probability, but the current method will not provide a unique
5002 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5003 #define DARK 1
5004 #define LITE 2
5005 #define ANY 3
5006
5007 int squaresLeft[4];
5008 int piecesLeft[(int)BlackPawn];
5009 int seed, nrOfShuffles;
5010
5011 void GetPositionNumber()
5012 {       // sets global variable seed
5013         int i;
5014
5015         seed = appData.defaultFrcPosition;
5016         if(seed < 0) { // randomize based on time for negative FRC position numbers
5017                 for(i=0; i<50; i++) seed += random();
5018                 seed = random() ^ random() >> 8 ^ random() << 8;
5019                 if(seed<0) seed = -seed;
5020         }
5021 }
5022
5023 int put(Board board, int pieceType, int rank, int n, int shade)
5024 // put the piece on the (n-1)-th empty squares of the given shade
5025 {
5026         int i;
5027
5028         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5029                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5030                         board[rank][i] = (ChessSquare) pieceType;
5031                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5032                         squaresLeft[ANY]--;
5033                         piecesLeft[pieceType]--; 
5034                         return i;
5035                 }
5036         }
5037         return -1;
5038 }
5039
5040
5041 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5042 // calculate where the next piece goes, (any empty square), and put it there
5043 {
5044         int i;
5045
5046         i = seed % squaresLeft[shade];
5047         nrOfShuffles *= squaresLeft[shade];
5048         seed /= squaresLeft[shade];
5049         put(board, pieceType, rank, i, shade);
5050 }
5051
5052 void AddTwoPieces(Board board, int pieceType, int rank)
5053 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5054 {
5055         int i, n=squaresLeft[ANY], j=n-1, k;
5056
5057         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5058         i = seed % k;  // pick one
5059         nrOfShuffles *= k;
5060         seed /= k;
5061         while(i >= j) i -= j--;
5062         j = n - 1 - j; i += j;
5063         put(board, pieceType, rank, j, ANY);
5064         put(board, pieceType, rank, i, ANY);
5065 }
5066
5067 void SetUpShuffle(Board board, int number)
5068 {
5069         int i, p, first=1;
5070
5071         GetPositionNumber(); nrOfShuffles = 1;
5072
5073         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5074         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5075         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5076
5077         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5078
5079         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5080             p = (int) board[0][i];
5081             if(p < (int) BlackPawn) piecesLeft[p] ++;
5082             board[0][i] = EmptySquare;
5083         }
5084
5085         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5086             // shuffles restricted to allow normal castling put KRR first
5087             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5088                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5089             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5090                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5091             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5092                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5093             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5094                 put(board, WhiteRook, 0, 0, ANY);
5095             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5096         }
5097
5098         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5099             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5100             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5101                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5102                 while(piecesLeft[p] >= 2) {
5103                     AddOnePiece(board, p, 0, LITE);
5104                     AddOnePiece(board, p, 0, DARK);
5105                 }
5106                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5107             }
5108
5109         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5110             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5111             // but we leave King and Rooks for last, to possibly obey FRC restriction
5112             if(p == (int)WhiteRook) continue;
5113             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5114             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5115         }
5116
5117         // now everything is placed, except perhaps King (Unicorn) and Rooks
5118
5119         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5120             // Last King gets castling rights
5121             while(piecesLeft[(int)WhiteUnicorn]) {
5122                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5123                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5124             }
5125
5126             while(piecesLeft[(int)WhiteKing]) {
5127                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5128                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5129             }
5130
5131
5132         } else {
5133             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5134             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5135         }
5136
5137         // Only Rooks can be left; simply place them all
5138         while(piecesLeft[(int)WhiteRook]) {
5139                 i = put(board, WhiteRook, 0, 0, ANY);
5140                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5141                         if(first) {
5142                                 first=0;
5143                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5144                         }
5145                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5146                 }
5147         }
5148         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5149             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5150         }
5151
5152         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5153 }
5154
5155 int SetCharTable( char *table, const char * map )
5156 /* [HGM] moved here from winboard.c because of its general usefulness */
5157 /*       Basically a safe strcpy that uses the last character as King */
5158 {
5159     int result = FALSE; int NrPieces;
5160
5161     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5162                     && NrPieces >= 12 && !(NrPieces&1)) {
5163         int i; /* [HGM] Accept even length from 12 to 34 */
5164
5165         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5166         for( i=0; i<NrPieces/2-1; i++ ) {
5167             table[i] = map[i];
5168             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5169         }
5170         table[(int) WhiteKing]  = map[NrPieces/2-1];
5171         table[(int) BlackKing]  = map[NrPieces-1];
5172
5173         result = TRUE;
5174     }
5175
5176     return result;
5177 }
5178
5179 void Prelude(Board board)
5180 {       // [HGM] superchess: random selection of exo-pieces
5181         int i, j, k; ChessSquare p; 
5182         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5183
5184         GetPositionNumber(); // use FRC position number
5185
5186         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5187             SetCharTable(pieceToChar, appData.pieceToCharTable);
5188             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5189                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5190         }
5191
5192         j = seed%4;                 seed /= 4; 
5193         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5194         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5195         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5196         j = seed%3 + (seed%3 >= j); seed /= 3; 
5197         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5198         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5199         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5200         j = seed%3;                 seed /= 3; 
5201         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5202         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5203         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5204         j = seed%2 + (seed%2 >= j); seed /= 2; 
5205         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5206         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5207         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5208         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5209         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5210         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5211         put(board, exoPieces[0],    0, 0, ANY);
5212         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5213 }
5214
5215 void
5216 InitPosition(redraw)
5217      int redraw;
5218 {
5219     ChessSquare (* pieces)[BOARD_FILES];
5220     int i, j, pawnRow, overrule,
5221     oldx = gameInfo.boardWidth,
5222     oldy = gameInfo.boardHeight,
5223     oldh = gameInfo.holdingsWidth,
5224     oldv = gameInfo.variant;
5225
5226     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5227
5228     /* [AS] Initialize pv info list [HGM] and game status */
5229     {
5230         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5231             pvInfoList[i].depth = 0;
5232             boards[i][EP_STATUS] = EP_NONE;
5233             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5234         }
5235
5236         initialRulePlies = 0; /* 50-move counter start */
5237
5238         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5239         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5240     }
5241
5242     
5243     /* [HGM] logic here is completely changed. In stead of full positions */
5244     /* the initialized data only consist of the two backranks. The switch */
5245     /* selects which one we will use, which is than copied to the Board   */
5246     /* initialPosition, which for the rest is initialized by Pawns and    */
5247     /* empty squares. This initial position is then copied to boards[0],  */
5248     /* possibly after shuffling, so that it remains available.            */
5249
5250     gameInfo.holdingsWidth = 0; /* default board sizes */
5251     gameInfo.boardWidth    = 8;
5252     gameInfo.boardHeight   = 8;
5253     gameInfo.holdingsSize  = 0;
5254     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5255     for(i=0; i<BOARD_FILES-2; i++)
5256       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5257     initialPosition[EP_STATUS] = EP_NONE;
5258     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5259     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5260          SetCharTable(pieceNickName, appData.pieceNickNames);
5261     else SetCharTable(pieceNickName, "............");
5262
5263     switch (gameInfo.variant) {
5264     case VariantFischeRandom:
5265       shuffleOpenings = TRUE;
5266     default:
5267       pieces = FIDEArray;
5268       break;
5269     case VariantShatranj:
5270       pieces = ShatranjArray;
5271       nrCastlingRights = 0;
5272       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5273       break;
5274     case VariantMakruk:
5275       pieces = makrukArray;
5276       nrCastlingRights = 0;
5277       startedFromSetupPosition = TRUE;
5278       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5279       break;
5280     case VariantTwoKings:
5281       pieces = twoKingsArray;
5282       break;
5283     case VariantCapaRandom:
5284       shuffleOpenings = TRUE;
5285     case VariantCapablanca:
5286       pieces = CapablancaArray;
5287       gameInfo.boardWidth = 10;
5288       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5289       break;
5290     case VariantGothic:
5291       pieces = GothicArray;
5292       gameInfo.boardWidth = 10;
5293       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5294       break;
5295     case VariantJanus:
5296       pieces = JanusArray;
5297       gameInfo.boardWidth = 10;
5298       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5299       nrCastlingRights = 6;
5300         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5301         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5302         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5303         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5304         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5305         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5306       break;
5307     case VariantFalcon:
5308       pieces = FalconArray;
5309       gameInfo.boardWidth = 10;
5310       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5311       break;
5312     case VariantXiangqi:
5313       pieces = XiangqiArray;
5314       gameInfo.boardWidth  = 9;
5315       gameInfo.boardHeight = 10;
5316       nrCastlingRights = 0;
5317       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5318       break;
5319     case VariantShogi:
5320       pieces = ShogiArray;
5321       gameInfo.boardWidth  = 9;
5322       gameInfo.boardHeight = 9;
5323       gameInfo.holdingsSize = 7;
5324       nrCastlingRights = 0;
5325       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5326       break;
5327     case VariantCourier:
5328       pieces = CourierArray;
5329       gameInfo.boardWidth  = 12;
5330       nrCastlingRights = 0;
5331       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5332       break;
5333     case VariantKnightmate:
5334       pieces = KnightmateArray;
5335       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5336       break;
5337     case VariantFairy:
5338       pieces = fairyArray;
5339       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5340       break;
5341     case VariantGreat:
5342       pieces = GreatArray;
5343       gameInfo.boardWidth = 10;
5344       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5345       gameInfo.holdingsSize = 8;
5346       break;
5347     case VariantSuper:
5348       pieces = FIDEArray;
5349       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5350       gameInfo.holdingsSize = 8;
5351       startedFromSetupPosition = TRUE;
5352       break;
5353     case VariantCrazyhouse:
5354     case VariantBughouse:
5355       pieces = FIDEArray;
5356       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5357       gameInfo.holdingsSize = 5;
5358       break;
5359     case VariantWildCastle:
5360       pieces = FIDEArray;
5361       /* !!?shuffle with kings guaranteed to be on d or e file */
5362       shuffleOpenings = 1;
5363       break;
5364     case VariantNoCastle:
5365       pieces = FIDEArray;
5366       nrCastlingRights = 0;
5367       /* !!?unconstrained back-rank shuffle */
5368       shuffleOpenings = 1;
5369       break;
5370     }
5371
5372     overrule = 0;
5373     if(appData.NrFiles >= 0) {
5374         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5375         gameInfo.boardWidth = appData.NrFiles;
5376     }
5377     if(appData.NrRanks >= 0) {
5378         gameInfo.boardHeight = appData.NrRanks;
5379     }
5380     if(appData.holdingsSize >= 0) {
5381         i = appData.holdingsSize;
5382         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5383         gameInfo.holdingsSize = i;
5384     }
5385     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5386     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5387         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5388
5389     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5390     if(pawnRow < 1) pawnRow = 1;
5391     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5392
5393     /* User pieceToChar list overrules defaults */
5394     if(appData.pieceToCharTable != NULL)
5395         SetCharTable(pieceToChar, appData.pieceToCharTable);
5396
5397     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5398
5399         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5400             s = (ChessSquare) 0; /* account holding counts in guard band */
5401         for( i=0; i<BOARD_HEIGHT; i++ )
5402             initialPosition[i][j] = s;
5403
5404         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5405         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5406         initialPosition[pawnRow][j] = WhitePawn;
5407         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5408         if(gameInfo.variant == VariantXiangqi) {
5409             if(j&1) {
5410                 initialPosition[pawnRow][j] = 
5411                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5412                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5413                    initialPosition[2][j] = WhiteCannon;
5414                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5415                 }
5416             }
5417         }
5418         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5419     }
5420     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5421
5422             j=BOARD_LEFT+1;
5423             initialPosition[1][j] = WhiteBishop;
5424             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5425             j=BOARD_RGHT-2;
5426             initialPosition[1][j] = WhiteRook;
5427             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5428     }
5429
5430     if( nrCastlingRights == -1) {
5431         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5432         /*       This sets default castling rights from none to normal corners   */
5433         /* Variants with other castling rights must set them themselves above    */
5434         nrCastlingRights = 6;
5435        
5436         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5437         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5438         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5439         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5440         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5441         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5442      }
5443
5444      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5445      if(gameInfo.variant == VariantGreat) { // promotion commoners
5446         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5447         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5448         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5449         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5450      }
5451   if (appData.debugMode) {
5452     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5453   }
5454     if(shuffleOpenings) {
5455         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5456         startedFromSetupPosition = TRUE;
5457     }
5458     if(startedFromPositionFile) {
5459       /* [HGM] loadPos: use PositionFile for every new game */
5460       CopyBoard(initialPosition, filePosition);
5461       for(i=0; i<nrCastlingRights; i++)
5462           initialRights[i] = filePosition[CASTLING][i];
5463       startedFromSetupPosition = TRUE;
5464     }
5465
5466     CopyBoard(boards[0], initialPosition);
5467
5468     if(oldx != gameInfo.boardWidth ||
5469        oldy != gameInfo.boardHeight ||
5470        oldh != gameInfo.holdingsWidth
5471 #ifdef GOTHIC
5472        || oldv == VariantGothic ||        // For licensing popups
5473        gameInfo.variant == VariantGothic
5474 #endif
5475 #ifdef FALCON
5476        || oldv == VariantFalcon ||
5477        gameInfo.variant == VariantFalcon
5478 #endif
5479                                          )
5480             InitDrawingSizes(-2 ,0);
5481
5482     if (redraw)
5483       DrawPosition(TRUE, boards[currentMove]);
5484 }
5485
5486 void
5487 SendBoard(cps, moveNum)
5488      ChessProgramState *cps;
5489      int moveNum;
5490 {
5491     char message[MSG_SIZ];
5492     
5493     if (cps->useSetboard) {
5494       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5495       sprintf(message, "setboard %s\n", fen);
5496       SendToProgram(message, cps);
5497       free(fen);
5498
5499     } else {
5500       ChessSquare *bp;
5501       int i, j;
5502       /* Kludge to set black to move, avoiding the troublesome and now
5503        * deprecated "black" command.
5504        */
5505       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5506
5507       SendToProgram("edit\n", cps);
5508       SendToProgram("#\n", cps);
5509       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5510         bp = &boards[moveNum][i][BOARD_LEFT];
5511         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5512           if ((int) *bp < (int) BlackPawn) {
5513             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5514                     AAA + j, ONE + i);
5515             if(message[0] == '+' || message[0] == '~') {
5516                 sprintf(message, "%c%c%c+\n",
5517                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5518                         AAA + j, ONE + i);
5519             }
5520             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5521                 message[1] = BOARD_RGHT   - 1 - j + '1';
5522                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5523             }
5524             SendToProgram(message, cps);
5525           }
5526         }
5527       }
5528     
5529       SendToProgram("c\n", cps);
5530       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5531         bp = &boards[moveNum][i][BOARD_LEFT];
5532         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5533           if (((int) *bp != (int) EmptySquare)
5534               && ((int) *bp >= (int) BlackPawn)) {
5535             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5536                     AAA + j, ONE + i);
5537             if(message[0] == '+' || message[0] == '~') {
5538                 sprintf(message, "%c%c%c+\n",
5539                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5540                         AAA + j, ONE + i);
5541             }
5542             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5543                 message[1] = BOARD_RGHT   - 1 - j + '1';
5544                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5545             }
5546             SendToProgram(message, cps);
5547           }
5548         }
5549       }
5550     
5551       SendToProgram(".\n", cps);
5552     }
5553     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5554 }
5555
5556 static int autoQueen; // [HGM] oneclick
5557
5558 int
5559 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5560 {
5561     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5562     /* [HGM] add Shogi promotions */
5563     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5564     ChessSquare piece;
5565     ChessMove moveType;
5566     Boolean premove;
5567
5568     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5569     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5570
5571     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5572       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5573         return FALSE;
5574
5575     piece = boards[currentMove][fromY][fromX];
5576     if(gameInfo.variant == VariantShogi) {
5577         promotionZoneSize = BOARD_HEIGHT/3;
5578         highestPromotingPiece = (int)WhiteFerz;
5579     } else if(gameInfo.variant == VariantMakruk) {
5580         promotionZoneSize = 3;
5581     }
5582
5583     // next weed out all moves that do not touch the promotion zone at all
5584     if((int)piece >= BlackPawn) {
5585         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5586              return FALSE;
5587         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5588     } else {
5589         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5590            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5591     }
5592
5593     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5594
5595     // weed out mandatory Shogi promotions
5596     if(gameInfo.variant == VariantShogi) {
5597         if(piece >= BlackPawn) {
5598             if(toY == 0 && piece == BlackPawn ||
5599                toY == 0 && piece == BlackQueen ||
5600                toY <= 1 && piece == BlackKnight) {
5601                 *promoChoice = '+';
5602                 return FALSE;
5603             }
5604         } else {
5605             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5606                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5607                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5608                 *promoChoice = '+';
5609                 return FALSE;
5610             }
5611         }
5612     }
5613
5614     // weed out obviously illegal Pawn moves
5615     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5616         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5617         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5618         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5619         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5620         // note we are not allowed to test for valid (non-)capture, due to premove
5621     }
5622
5623     // we either have a choice what to promote to, or (in Shogi) whether to promote
5624     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5625         *promoChoice = PieceToChar(BlackFerz);  // no choice
5626         return FALSE;
5627     }
5628     if(autoQueen) { // predetermined
5629         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5630              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5631         else *promoChoice = PieceToChar(BlackQueen);
5632         return FALSE;
5633     }
5634
5635     // suppress promotion popup on illegal moves that are not premoves
5636     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5637               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5638     if(appData.testLegality && !premove) {
5639         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5640                         fromY, fromX, toY, toX, NULLCHAR);
5641         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5642             return FALSE;
5643     }
5644
5645     return TRUE;
5646 }
5647
5648 int
5649 InPalace(row, column)
5650      int row, column;
5651 {   /* [HGM] for Xiangqi */
5652     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5653          column < (BOARD_WIDTH + 4)/2 &&
5654          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5655     return FALSE;
5656 }
5657
5658 int
5659 PieceForSquare (x, y)
5660      int x;
5661      int y;
5662 {
5663   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5664      return -1;
5665   else
5666      return boards[currentMove][y][x];
5667 }
5668
5669 int
5670 OKToStartUserMove(x, y)
5671      int x, y;
5672 {
5673     ChessSquare from_piece;
5674     int white_piece;
5675
5676     if (matchMode) return FALSE;
5677     if (gameMode == EditPosition) return TRUE;
5678
5679     if (x >= 0 && y >= 0)
5680       from_piece = boards[currentMove][y][x];
5681     else
5682       from_piece = EmptySquare;
5683
5684     if (from_piece == EmptySquare) return FALSE;
5685
5686     white_piece = (int)from_piece >= (int)WhitePawn &&
5687       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5688
5689     switch (gameMode) {
5690       case PlayFromGameFile:
5691       case AnalyzeFile:
5692       case TwoMachinesPlay:
5693       case EndOfGame:
5694         return FALSE;
5695
5696       case IcsObserving:
5697       case IcsIdle:
5698         return FALSE;
5699
5700       case MachinePlaysWhite:
5701       case IcsPlayingBlack:
5702         if (appData.zippyPlay) return FALSE;
5703         if (white_piece) {
5704             DisplayMoveError(_("You are playing Black"));
5705             return FALSE;
5706         }
5707         break;
5708
5709       case MachinePlaysBlack:
5710       case IcsPlayingWhite:
5711         if (appData.zippyPlay) return FALSE;
5712         if (!white_piece) {
5713             DisplayMoveError(_("You are playing White"));
5714             return FALSE;
5715         }
5716         break;
5717
5718       case EditGame:
5719         if (!white_piece && WhiteOnMove(currentMove)) {
5720             DisplayMoveError(_("It is White's turn"));
5721             return FALSE;
5722         }           
5723         if (white_piece && !WhiteOnMove(currentMove)) {
5724             DisplayMoveError(_("It is Black's turn"));
5725             return FALSE;
5726         }           
5727         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5728             /* Editing correspondence game history */
5729             /* Could disallow this or prompt for confirmation */
5730             cmailOldMove = -1;
5731         }
5732         break;
5733
5734       case BeginningOfGame:
5735         if (appData.icsActive) return FALSE;
5736         if (!appData.noChessProgram) {
5737             if (!white_piece) {
5738                 DisplayMoveError(_("You are playing White"));
5739                 return FALSE;
5740             }
5741         }
5742         break;
5743         
5744       case Training:
5745         if (!white_piece && WhiteOnMove(currentMove)) {
5746             DisplayMoveError(_("It is White's turn"));
5747             return FALSE;
5748         }           
5749         if (white_piece && !WhiteOnMove(currentMove)) {
5750             DisplayMoveError(_("It is Black's turn"));
5751             return FALSE;
5752         }           
5753         break;
5754
5755       default:
5756       case IcsExamining:
5757         break;
5758     }
5759     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5760         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5761         && gameMode != AnalyzeFile && gameMode != Training) {
5762         DisplayMoveError(_("Displayed position is not current"));
5763         return FALSE;
5764     }
5765     return TRUE;
5766 }
5767
5768 Boolean
5769 OnlyMove(int *x, int *y, Boolean captures) {
5770     DisambiguateClosure cl;
5771     if (appData.zippyPlay) return FALSE;
5772     switch(gameMode) {
5773       case MachinePlaysBlack:
5774       case IcsPlayingWhite:
5775       case BeginningOfGame:
5776         if(!WhiteOnMove(currentMove)) return FALSE;
5777         break;
5778       case MachinePlaysWhite:
5779       case IcsPlayingBlack:
5780         if(WhiteOnMove(currentMove)) return FALSE;
5781         break;
5782       default:
5783         return FALSE;
5784     }
5785     cl.pieceIn = EmptySquare; 
5786     cl.rfIn = *y;
5787     cl.ffIn = *x;
5788     cl.rtIn = -1;
5789     cl.ftIn = -1;
5790     cl.promoCharIn = NULLCHAR;
5791     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5792     if( cl.kind == NormalMove ||
5793         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5794         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5795         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5796       fromX = cl.ff;
5797       fromY = cl.rf;
5798       *x = cl.ft;
5799       *y = cl.rt;
5800       return TRUE;
5801     }
5802     if(cl.kind != ImpossibleMove) return FALSE;
5803     cl.pieceIn = EmptySquare;
5804     cl.rfIn = -1;
5805     cl.ffIn = -1;
5806     cl.rtIn = *y;
5807     cl.ftIn = *x;
5808     cl.promoCharIn = NULLCHAR;
5809     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5810     if( cl.kind == NormalMove ||
5811         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5812         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5813         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5814       fromX = cl.ff;
5815       fromY = cl.rf;
5816       *x = cl.ft;
5817       *y = cl.rt;
5818       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5819       return TRUE;
5820     }
5821     return FALSE;
5822 }
5823
5824 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5825 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5826 int lastLoadGameUseList = FALSE;
5827 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5828 ChessMove lastLoadGameStart = (ChessMove) 0;
5829
5830 void
5831 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5832      int fromX, fromY, toX, toY;
5833      int promoChar;
5834 {
5835     ChessMove moveType;
5836     ChessSquare pdown, pup;
5837
5838     /* Check if the user is playing in turn.  This is complicated because we
5839        let the user "pick up" a piece before it is his turn.  So the piece he
5840        tried to pick up may have been captured by the time he puts it down!
5841        Therefore we use the color the user is supposed to be playing in this
5842        test, not the color of the piece that is currently on the starting
5843        square---except in EditGame mode, where the user is playing both
5844        sides; fortunately there the capture race can't happen.  (It can
5845        now happen in IcsExamining mode, but that's just too bad.  The user
5846        will get a somewhat confusing message in that case.)
5847        */
5848
5849     switch (gameMode) {
5850       case PlayFromGameFile:
5851       case AnalyzeFile:
5852       case TwoMachinesPlay:
5853       case EndOfGame:
5854       case IcsObserving:
5855       case IcsIdle:
5856         /* We switched into a game mode where moves are not accepted,
5857            perhaps while the mouse button was down. */
5858         return;
5859
5860       case MachinePlaysWhite:
5861         /* User is moving for Black */
5862         if (WhiteOnMove(currentMove)) {
5863             DisplayMoveError(_("It is White's turn"));
5864             return;
5865         }
5866         break;
5867
5868       case MachinePlaysBlack:
5869         /* User is moving for White */
5870         if (!WhiteOnMove(currentMove)) {
5871             DisplayMoveError(_("It is Black's turn"));
5872             return;
5873         }
5874         break;
5875
5876       case EditGame:
5877       case IcsExamining:
5878       case BeginningOfGame:
5879       case AnalyzeMode:
5880       case Training:
5881         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5882             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5883             /* User is moving for Black */
5884             if (WhiteOnMove(currentMove)) {
5885                 DisplayMoveError(_("It is White's turn"));
5886                 return;
5887             }
5888         } else {
5889             /* User is moving for White */
5890             if (!WhiteOnMove(currentMove)) {
5891                 DisplayMoveError(_("It is Black's turn"));
5892                 return;
5893             }
5894         }
5895         break;
5896
5897       case IcsPlayingBlack:
5898         /* User is moving for Black */
5899         if (WhiteOnMove(currentMove)) {
5900             if (!appData.premove) {
5901                 DisplayMoveError(_("It is White's turn"));
5902             } else if (toX >= 0 && toY >= 0) {
5903                 premoveToX = toX;
5904                 premoveToY = toY;
5905                 premoveFromX = fromX;
5906                 premoveFromY = fromY;
5907                 premovePromoChar = promoChar;
5908                 gotPremove = 1;
5909                 if (appData.debugMode) 
5910                     fprintf(debugFP, "Got premove: fromX %d,"
5911                             "fromY %d, toX %d, toY %d\n",
5912                             fromX, fromY, toX, toY);
5913             }
5914             return;
5915         }
5916         break;
5917
5918       case IcsPlayingWhite:
5919         /* User is moving for White */
5920         if (!WhiteOnMove(currentMove)) {
5921             if (!appData.premove) {
5922                 DisplayMoveError(_("It is Black's turn"));
5923             } else if (toX >= 0 && toY >= 0) {
5924                 premoveToX = toX;
5925                 premoveToY = toY;
5926                 premoveFromX = fromX;
5927                 premoveFromY = fromY;
5928                 premovePromoChar = promoChar;
5929                 gotPremove = 1;
5930                 if (appData.debugMode) 
5931                     fprintf(debugFP, "Got premove: fromX %d,"
5932                             "fromY %d, toX %d, toY %d\n",
5933                             fromX, fromY, toX, toY);
5934             }
5935             return;
5936         }
5937         break;
5938
5939       default:
5940         break;
5941
5942       case EditPosition:
5943         /* EditPosition, empty square, or different color piece;
5944            click-click move is possible */
5945         if (toX == -2 || toY == -2) {
5946             boards[0][fromY][fromX] = EmptySquare;
5947             DrawPosition(FALSE, boards[currentMove]);
5948             return;
5949         } else if (toX >= 0 && toY >= 0) {
5950             boards[0][toY][toX] = boards[0][fromY][fromX];
5951             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5952                 if(boards[0][fromY][0] != EmptySquare) {
5953                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5954                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5955                 }
5956             } else
5957             if(fromX == BOARD_RGHT+1) {
5958                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5959                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5960                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5961                 }
5962             } else
5963             boards[0][fromY][fromX] = EmptySquare;
5964             DrawPosition(FALSE, boards[currentMove]);
5965             return;
5966         }
5967         return;
5968     }
5969
5970     if(toX < 0 || toY < 0) return;
5971     pdown = boards[currentMove][fromY][fromX];
5972     pup = boards[currentMove][toY][toX];
5973
5974     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
5975     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5976          if( pup != EmptySquare ) return;
5977          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5978            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5979                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5980            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5981            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5982            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5983            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5984          fromY = DROP_RANK;
5985     }
5986
5987     /* [HGM] always test for legality, to get promotion info */
5988     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5989                                          fromY, fromX, toY, toX, promoChar);
5990     /* [HGM] but possibly ignore an IllegalMove result */
5991     if (appData.testLegality) {
5992         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5993             DisplayMoveError(_("Illegal move"));
5994             return;
5995         }
5996     }
5997
5998     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5999 }
6000
6001 /* Common tail of UserMoveEvent and DropMenuEvent */
6002 int
6003 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6004      ChessMove moveType;
6005      int fromX, fromY, toX, toY;
6006      /*char*/int promoChar;
6007 {
6008     char *bookHit = 0;
6009
6010     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6011         // [HGM] superchess: suppress promotions to non-available piece
6012         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6013         if(WhiteOnMove(currentMove)) {
6014             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6015         } else {
6016             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6017         }
6018     }
6019
6020     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6021        move type in caller when we know the move is a legal promotion */
6022     if(moveType == NormalMove && promoChar)
6023         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6024
6025     /* [HGM] <popupFix> The following if has been moved here from
6026        UserMoveEvent(). Because it seemed to belong here (why not allow
6027        piece drops in training games?), and because it can only be
6028        performed after it is known to what we promote. */
6029     if (gameMode == Training) {
6030       /* compare the move played on the board to the next move in the
6031        * game. If they match, display the move and the opponent's response. 
6032        * If they don't match, display an error message.
6033        */
6034       int saveAnimate;
6035       Board testBoard;
6036       CopyBoard(testBoard, boards[currentMove]);
6037       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6038
6039       if (CompareBoards(testBoard, boards[currentMove+1])) {
6040         ForwardInner(currentMove+1);
6041
6042         /* Autoplay the opponent's response.
6043          * if appData.animate was TRUE when Training mode was entered,
6044          * the response will be animated.
6045          */
6046         saveAnimate = appData.animate;
6047         appData.animate = animateTraining;
6048         ForwardInner(currentMove+1);
6049         appData.animate = saveAnimate;
6050
6051         /* check for the end of the game */
6052         if (currentMove >= forwardMostMove) {
6053           gameMode = PlayFromGameFile;
6054           ModeHighlight();
6055           SetTrainingModeOff();
6056           DisplayInformation(_("End of game"));
6057         }
6058       } else {
6059         DisplayError(_("Incorrect move"), 0);
6060       }
6061       return 1;
6062     }
6063
6064   /* Ok, now we know that the move is good, so we can kill
6065      the previous line in Analysis Mode */
6066   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6067                                 && currentMove < forwardMostMove) {
6068     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6069   }
6070
6071   /* If we need the chess program but it's dead, restart it */
6072   ResurrectChessProgram();
6073
6074   /* A user move restarts a paused game*/
6075   if (pausing)
6076     PauseEvent();
6077
6078   thinkOutput[0] = NULLCHAR;
6079
6080   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6081
6082   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6083
6084   if (gameMode == BeginningOfGame) {
6085     if (appData.noChessProgram) {
6086       gameMode = EditGame;
6087       SetGameInfo();
6088     } else {
6089       char buf[MSG_SIZ];
6090       gameMode = MachinePlaysBlack;
6091       StartClocks();
6092       SetGameInfo();
6093       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6094       DisplayTitle(buf);
6095       if (first.sendName) {
6096         sprintf(buf, "name %s\n", gameInfo.white);
6097         SendToProgram(buf, &first);
6098       }
6099       StartClocks();
6100     }
6101     ModeHighlight();
6102   }
6103
6104   /* Relay move to ICS or chess engine */
6105   if (appData.icsActive) {
6106     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6107         gameMode == IcsExamining) {
6108       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6109         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6110         SendToICS("draw ");
6111         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6112       }
6113       // also send plain move, in case ICS does not understand atomic claims
6114       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6115       ics_user_moved = 1;
6116     }
6117   } else {
6118     if (first.sendTime && (gameMode == BeginningOfGame ||
6119                            gameMode == MachinePlaysWhite ||
6120                            gameMode == MachinePlaysBlack)) {
6121       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6122     }
6123     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6124          // [HGM] book: if program might be playing, let it use book
6125         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6126         first.maybeThinking = TRUE;
6127     } else SendMoveToProgram(forwardMostMove-1, &first);
6128     if (currentMove == cmailOldMove + 1) {
6129       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6130     }
6131   }
6132
6133   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6134
6135   switch (gameMode) {
6136   case EditGame:
6137     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6138     case MT_NONE:
6139     case MT_CHECK:
6140       break;
6141     case MT_CHECKMATE:
6142     case MT_STAINMATE:
6143       if (WhiteOnMove(currentMove)) {
6144         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6145       } else {
6146         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6147       }
6148       break;
6149     case MT_STALEMATE:
6150       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6151       break;
6152     }
6153     break;
6154     
6155   case MachinePlaysBlack:
6156   case MachinePlaysWhite:
6157     /* disable certain menu options while machine is thinking */
6158     SetMachineThinkingEnables();
6159     break;
6160
6161   default:
6162     break;
6163   }
6164
6165   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6166         
6167   if(bookHit) { // [HGM] book: simulate book reply
6168         static char bookMove[MSG_SIZ]; // a bit generous?
6169
6170         programStats.nodes = programStats.depth = programStats.time = 
6171         programStats.score = programStats.got_only_move = 0;
6172         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6173
6174         strcpy(bookMove, "move ");
6175         strcat(bookMove, bookHit);
6176         HandleMachineMove(bookMove, &first);
6177   }
6178   return 1;
6179 }
6180
6181 void
6182 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6183      Board board;
6184      int flags;
6185      ChessMove kind;
6186      int rf, ff, rt, ft;
6187      VOIDSTAR closure;
6188 {
6189     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6190     Markers *m = (Markers *) closure;
6191     if(rf == fromY && ff == fromX)
6192         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6193                          || kind == WhiteCapturesEnPassant
6194                          || kind == BlackCapturesEnPassant);
6195     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6196 }
6197
6198 void
6199 MarkTargetSquares(int clear)
6200 {
6201   int x, y;
6202   if(!appData.markers || !appData.highlightDragging || 
6203      !appData.testLegality || gameMode == EditPosition) return;
6204   if(clear) {
6205     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6206   } else {
6207     int capt = 0;
6208     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6209     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6210       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6211       if(capt)
6212       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6213     }
6214   }
6215   DrawPosition(TRUE, NULL);
6216 }
6217
6218 void LeftClick(ClickType clickType, int xPix, int yPix)
6219 {
6220     int x, y;
6221     Boolean saveAnimate;
6222     static int second = 0, promotionChoice = 0, dragging = 0;
6223     char promoChoice = NULLCHAR;
6224
6225     if(appData.seekGraph && appData.icsActive && loggedOn &&
6226         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6227         SeekGraphClick(clickType, xPix, yPix, 0);
6228         return;
6229     }
6230
6231     if (clickType == Press) ErrorPopDown();
6232     MarkTargetSquares(1);
6233
6234     x = EventToSquare(xPix, BOARD_WIDTH);
6235     y = EventToSquare(yPix, BOARD_HEIGHT);
6236     if (!flipView && y >= 0) {
6237         y = BOARD_HEIGHT - 1 - y;
6238     }
6239     if (flipView && x >= 0) {
6240         x = BOARD_WIDTH - 1 - x;
6241     }
6242
6243     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6244         if(clickType == Release) return; // ignore upclick of click-click destination
6245         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6246         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6247         if(gameInfo.holdingsWidth && 
6248                 (WhiteOnMove(currentMove) 
6249                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6250                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6251             // click in right holdings, for determining promotion piece
6252             ChessSquare p = boards[currentMove][y][x];
6253             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6254             if(p != EmptySquare) {
6255                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6256                 fromX = fromY = -1;
6257                 return;
6258             }
6259         }
6260         DrawPosition(FALSE, boards[currentMove]);
6261         return;
6262     }
6263
6264     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6265     if(clickType == Press
6266             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6267               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6268               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6269         return;
6270
6271     autoQueen = appData.alwaysPromoteToQueen;
6272
6273     if (fromX == -1) {
6274       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6275         if (clickType == Press) {
6276             /* First square */
6277             if (OKToStartUserMove(x, y)) {
6278                 fromX = x;
6279                 fromY = y;
6280                 second = 0;
6281                 MarkTargetSquares(0);
6282                 DragPieceBegin(xPix, yPix); dragging = 1;
6283                 if (appData.highlightDragging) {
6284                     SetHighlights(x, y, -1, -1);
6285                 }
6286             }
6287         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6288             DragPieceEnd(xPix, yPix); dragging = 0;
6289             DrawPosition(FALSE, NULL);
6290         }
6291         return;
6292       }
6293     }
6294
6295     /* fromX != -1 */
6296     if (clickType == Press && gameMode != EditPosition) {
6297         ChessSquare fromP;
6298         ChessSquare toP;
6299         int frc;
6300
6301         // ignore off-board to clicks
6302         if(y < 0 || x < 0) return;
6303
6304         /* Check if clicking again on the same color piece */
6305         fromP = boards[currentMove][fromY][fromX];
6306         toP = boards[currentMove][y][x];
6307         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6308         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6309              WhitePawn <= toP && toP <= WhiteKing &&
6310              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6311              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6312             (BlackPawn <= fromP && fromP <= BlackKing && 
6313              BlackPawn <= toP && toP <= BlackKing &&
6314              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6315              !(fromP == BlackKing && toP == BlackRook && frc))) {
6316             /* Clicked again on same color piece -- changed his mind */
6317             second = (x == fromX && y == fromY);
6318            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6319             if (appData.highlightDragging) {
6320                 SetHighlights(x, y, -1, -1);
6321             } else {
6322                 ClearHighlights();
6323             }
6324             if (OKToStartUserMove(x, y)) {
6325                 fromX = x;
6326                 fromY = y; dragging = 1;
6327                 MarkTargetSquares(0);
6328                 DragPieceBegin(xPix, yPix);
6329             }
6330             return;
6331            }
6332         }
6333         // ignore clicks on holdings
6334         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6335     }
6336
6337     if (clickType == Release && x == fromX && y == fromY) {
6338         DragPieceEnd(xPix, yPix); dragging = 0;
6339         if (appData.animateDragging) {
6340             /* Undo animation damage if any */
6341             DrawPosition(FALSE, NULL);
6342         }
6343         if (second) {
6344             /* Second up/down in same square; just abort move */
6345             second = 0;
6346             fromX = fromY = -1;
6347             ClearHighlights();
6348             gotPremove = 0;
6349             ClearPremoveHighlights();
6350         } else {
6351             /* First upclick in same square; start click-click mode */
6352             SetHighlights(x, y, -1, -1);
6353         }
6354         return;
6355     }
6356
6357     /* we now have a different from- and (possibly off-board) to-square */
6358     /* Completed move */
6359     toX = x;
6360     toY = y;
6361     saveAnimate = appData.animate;
6362     if (clickType == Press) {
6363         /* Finish clickclick move */
6364         if (appData.animate || appData.highlightLastMove) {
6365             SetHighlights(fromX, fromY, toX, toY);
6366         } else {
6367             ClearHighlights();
6368         }
6369     } else {
6370         /* Finish drag move */
6371         if (appData.highlightLastMove) {
6372             SetHighlights(fromX, fromY, toX, toY);
6373         } else {
6374             ClearHighlights();
6375         }
6376         DragPieceEnd(xPix, yPix); dragging = 0;
6377         /* Don't animate move and drag both */
6378         appData.animate = FALSE;
6379     }
6380
6381     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6382     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6383         ChessSquare piece = boards[currentMove][fromY][fromX];
6384         if(gameMode == EditPosition && piece != EmptySquare &&
6385            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6386             int n;
6387              
6388             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6389                 n = PieceToNumber(piece - (int)BlackPawn);
6390                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6391                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6392                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6393             } else
6394             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6395                 n = PieceToNumber(piece);
6396                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6397                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6398                 boards[currentMove][n][BOARD_WIDTH-2]++;
6399             }
6400             boards[currentMove][fromY][fromX] = EmptySquare;
6401         }
6402         ClearHighlights();
6403         fromX = fromY = -1;
6404         DrawPosition(TRUE, boards[currentMove]);
6405         return;
6406     }
6407
6408     // off-board moves should not be highlighted
6409     if(x < 0 || x < 0) ClearHighlights();
6410
6411     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6412         SetHighlights(fromX, fromY, toX, toY);
6413         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6414             // [HGM] super: promotion to captured piece selected from holdings
6415             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6416             promotionChoice = TRUE;
6417             // kludge follows to temporarily execute move on display, without promoting yet
6418             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6419             boards[currentMove][toY][toX] = p;
6420             DrawPosition(FALSE, boards[currentMove]);
6421             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6422             boards[currentMove][toY][toX] = q;
6423             DisplayMessage("Click in holdings to choose piece", "");
6424             return;
6425         }
6426         PromotionPopUp();
6427     } else {
6428         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6429         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6430         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6431         fromX = fromY = -1;
6432     }
6433     appData.animate = saveAnimate;
6434     if (appData.animate || appData.animateDragging) {
6435         /* Undo animation damage if needed */
6436         DrawPosition(FALSE, NULL);
6437     }
6438 }
6439
6440 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6441 {   // front-end-free part taken out of PieceMenuPopup
6442     int whichMenu; int xSqr, ySqr;
6443
6444     if(seekGraphUp) { // [HGM] seekgraph
6445         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6446         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6447         return -2;
6448     }
6449
6450     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6451          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6452         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6453         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6454         if(action == Press)   {
6455             originalFlip = flipView;
6456             flipView = !flipView; // temporarily flip board to see game from partners perspective
6457             DrawPosition(TRUE, partnerBoard);
6458             DisplayMessage(partnerStatus, "");
6459             partnerUp = TRUE;
6460         } else if(action == Release) {
6461             flipView = originalFlip;
6462             DrawPosition(TRUE, boards[currentMove]);
6463             partnerUp = FALSE;
6464         }
6465         return -2;
6466     }
6467
6468     xSqr = EventToSquare(x, BOARD_WIDTH);
6469     ySqr = EventToSquare(y, BOARD_HEIGHT);
6470     if (action == Release) UnLoadPV(); // [HGM] pv
6471     if (action != Press) return -2; // return code to be ignored
6472     switch (gameMode) {
6473       case IcsExamining:
6474         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6475       case EditPosition:
6476         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6477         if (xSqr < 0 || ySqr < 0) return -1;\r
6478         whichMenu = 0; // edit-position menu
6479         break;
6480       case IcsObserving:
6481         if(!appData.icsEngineAnalyze) return -1;
6482       case IcsPlayingWhite:
6483       case IcsPlayingBlack:
6484         if(!appData.zippyPlay) goto noZip;
6485       case AnalyzeMode:
6486       case AnalyzeFile:
6487       case MachinePlaysWhite:
6488       case MachinePlaysBlack:
6489       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6490         if (!appData.dropMenu) {
6491           LoadPV(x, y);
6492           return 2; // flag front-end to grab mouse events
6493         }
6494         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6495            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6496       case EditGame:
6497       noZip:
6498         if (xSqr < 0 || ySqr < 0) return -1;
6499         if (!appData.dropMenu || appData.testLegality &&
6500             gameInfo.variant != VariantBughouse &&
6501             gameInfo.variant != VariantCrazyhouse) return -1;
6502         whichMenu = 1; // drop menu
6503         break;
6504       default:
6505         return -1;
6506     }
6507
6508     if (((*fromX = xSqr) < 0) ||
6509         ((*fromY = ySqr) < 0)) {
6510         *fromX = *fromY = -1;
6511         return -1;
6512     }
6513     if (flipView)
6514       *fromX = BOARD_WIDTH - 1 - *fromX;
6515     else
6516       *fromY = BOARD_HEIGHT - 1 - *fromY;
6517
6518     return whichMenu;
6519 }
6520
6521 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6522 {
6523 //    char * hint = lastHint;
6524     FrontEndProgramStats stats;
6525
6526     stats.which = cps == &first ? 0 : 1;
6527     stats.depth = cpstats->depth;
6528     stats.nodes = cpstats->nodes;
6529     stats.score = cpstats->score;
6530     stats.time = cpstats->time;
6531     stats.pv = cpstats->movelist;
6532     stats.hint = lastHint;
6533     stats.an_move_index = 0;
6534     stats.an_move_count = 0;
6535
6536     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6537         stats.hint = cpstats->move_name;
6538         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6539         stats.an_move_count = cpstats->nr_moves;
6540     }
6541
6542     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6543
6544     SetProgramStats( &stats );
6545 }
6546
6547 void
6548 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6549 {       // count all piece types
6550         int p, f, r;
6551         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6552         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6553         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6554                 p = board[r][f];
6555                 pCnt[p]++;
6556                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6557                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6558                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6559                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6560                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6561                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6562         }
6563 }
6564
6565 int
6566 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6567 {
6568         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6569         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6570                    
6571         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6572         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6573         if(myPawns == 2 && nMine == 3) // KPP
6574             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6575         if(myPawns == 1 && nMine == 2) // KP
6576             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6577         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6578             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6579         if(myPawns) return FALSE;
6580         if(pCnt[WhiteRook+side])
6581             return pCnt[BlackRook-side] || 
6582                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6583                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6584                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6585         if(pCnt[WhiteCannon+side]) {
6586             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6587             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6588         }
6589         if(pCnt[WhiteKnight+side])
6590             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6591         return FALSE;
6592 }
6593
6594 int
6595 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6596 {
6597         VariantClass v = gameInfo.variant;
6598
6599         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6600         if(v == VariantShatranj) return TRUE; // always winnable through baring
6601         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6602         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6603
6604         if(v == VariantXiangqi) {
6605                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6606
6607                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6608                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6609                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6610                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6611                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6612                 if(stale) // we have at least one last-rank P plus perhaps C
6613                     return majors // KPKX
6614                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6615                 else // KCA*E*
6616                     return pCnt[WhiteFerz+side] // KCAK
6617                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6618                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6619                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6620
6621         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6622                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6623                 
6624                 if(nMine == 1) return FALSE; // bare King
6625                 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
6626                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6627                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6628                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6629                 if(pCnt[WhiteKnight+side])
6630                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6631                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6632                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6633                 if(nBishops)
6634                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6635                 if(pCnt[WhiteAlfil+side])
6636                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6637                 if(pCnt[WhiteWazir+side])
6638                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6639         }
6640
6641         return TRUE;
6642 }
6643
6644 int
6645 Adjudicate(ChessProgramState *cps)
6646 {       // [HGM] some adjudications useful with buggy engines
6647         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6648         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6649         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6650         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6651         int k, count = 0; static int bare = 1;
6652         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6653         Boolean canAdjudicate = !appData.icsActive;
6654
6655         // most tests only when we understand the game, i.e. legality-checking on
6656             if( appData.testLegality )
6657             {   /* [HGM] Some more adjudications for obstinate engines */
6658                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6659                 static int moveCount = 6;
6660                 ChessMove result;
6661                 char *reason = NULL;
6662
6663                 /* Count what is on board. */
6664                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6665
6666                 /* Some material-based adjudications that have to be made before stalemate test */
6667                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6668                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6669                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6670                      if(canAdjudicate && appData.checkMates) {
6671                          if(engineOpponent)
6672                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6673                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6674                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6675                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6676                          return 1;
6677                      }
6678                 }
6679
6680                 /* Bare King in Shatranj (loses) or Losers (wins) */
6681                 if( nrW == 1 || nrB == 1) {
6682                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6683                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6684                      if(canAdjudicate && appData.checkMates) {
6685                          if(engineOpponent)
6686                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6687                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6688                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6689                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6690                          return 1;
6691                      }
6692                   } else
6693                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6694                   {    /* bare King */
6695                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6696                         if(canAdjudicate && appData.checkMates) {
6697                             /* but only adjudicate if adjudication enabled */
6698                             if(engineOpponent)
6699                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6700                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6701                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6702                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6703                             return 1;
6704                         }
6705                   }
6706                 } else bare = 1;
6707
6708
6709             // don't wait for engine to announce game end if we can judge ourselves
6710             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6711               case MT_CHECK:
6712                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6713                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6714                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6715                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6716                             checkCnt++;
6717                         if(checkCnt >= 2) {
6718                             reason = "Xboard adjudication: 3rd check";
6719                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6720                             break;
6721                         }
6722                     }
6723                 }
6724               case MT_NONE:
6725               default:
6726                 break;
6727               case MT_STALEMATE:
6728               case MT_STAINMATE:
6729                 reason = "Xboard adjudication: Stalemate";
6730                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6731                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6732                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6733                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6734                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6735                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6736                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6737                                                                         EP_CHECKMATE : EP_WINS);
6738                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6739                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6740                 }
6741                 break;
6742               case MT_CHECKMATE:
6743                 reason = "Xboard adjudication: Checkmate";
6744                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6745                 break;
6746             }
6747
6748                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6749                     case EP_STALEMATE:
6750                         result = GameIsDrawn; break;
6751                     case EP_CHECKMATE:
6752                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6753                     case EP_WINS:
6754                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6755                     default:
6756                         result = (ChessMove) 0;
6757                 }
6758                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6759                     if(engineOpponent)
6760                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6761                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6762                     GameEnds( result, reason, GE_XBOARD );
6763                     return 1;
6764                 }
6765
6766                 /* Next absolutely insufficient mating material. */
6767                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6768                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6769                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6770
6771                      /* always flag draws, for judging claims */
6772                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6773
6774                      if(canAdjudicate && appData.materialDraws) {
6775                          /* but only adjudicate them if adjudication enabled */
6776                          if(engineOpponent) {
6777                            SendToProgram("force\n", engineOpponent); // suppress reply
6778                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6779                          }
6780                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6781                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6782                          return 1;
6783                      }
6784                 }
6785
6786                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6787                 if(gameInfo.variant == VariantXiangqi ?
6788                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6789                  : nrW + nrB == 4 && 
6790                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6791                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6792                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6793                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6794                    ) ) {
6795                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6796                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6797                           if(engineOpponent) {
6798                             SendToProgram("force\n", engineOpponent); // suppress reply
6799                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6800                           }
6801                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6802                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6803                           return 1;
6804                      }
6805                 } else moveCount = 6;
6806             }
6807           
6808         if (appData.debugMode) { int i;
6809             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6810                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6811                     appData.drawRepeats);
6812             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6813               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6814             
6815         }
6816
6817         // Repetition draws and 50-move rule can be applied independently of legality testing
6818
6819                 /* Check for rep-draws */
6820                 count = 0;
6821                 for(k = forwardMostMove-2;
6822                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6823                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6824                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6825                     k-=2)
6826                 {   int rights=0;
6827                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6828                         /* compare castling rights */
6829                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6830                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6831                                 rights++; /* King lost rights, while rook still had them */
6832                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6833                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6834                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6835                                    rights++; /* but at least one rook lost them */
6836                         }
6837                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6838                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6839                                 rights++; 
6840                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6841                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6842                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6843                                    rights++;
6844                         }
6845                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6846                             && appData.drawRepeats > 1) {
6847                              /* adjudicate after user-specified nr of repeats */
6848                              int result = GameIsDrawn;
6849                              char *details = "XBoard adjudication: repetition draw";
6850                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6851                                 // [HGM] xiangqi: check for forbidden perpetuals
6852                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6853                                 for(m=forwardMostMove; m>k; m-=2) {
6854                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6855                                         ourPerpetual = 0; // the current mover did not always check
6856                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6857                                         hisPerpetual = 0; // the opponent did not always check
6858                                 }
6859                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6860                                                                         ourPerpetual, hisPerpetual);
6861                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6862                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6863                                     details = "Xboard adjudication: perpetual checking";
6864                                 } else
6865                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6866                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6867                                 } else
6868                                 // Now check for perpetual chases
6869                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6870                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6871                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6872                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6873                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6874                                         details = "Xboard adjudication: perpetual chasing";
6875                                     } else
6876                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6877                                         break; // Abort repetition-checking loop.
6878                                 }
6879                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6880                              }
6881                              if(engineOpponent) {
6882                                SendToProgram("force\n", engineOpponent); // suppress reply
6883                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6884                              }
6885                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6886                              GameEnds( result, details, GE_XBOARD );
6887                              return 1;
6888                         }
6889                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6890                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6891                     }
6892                 }
6893
6894                 /* Now we test for 50-move draws. Determine ply count */
6895                 count = forwardMostMove;
6896                 /* look for last irreversble move */
6897                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6898                     count--;
6899                 /* if we hit starting position, add initial plies */
6900                 if( count == backwardMostMove )
6901                     count -= initialRulePlies;
6902                 count = forwardMostMove - count; 
6903                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6904                         // adjust reversible move counter for checks in Xiangqi
6905                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6906                         if(i < backwardMostMove) i = backwardMostMove;
6907                         while(i <= forwardMostMove) {
6908                                 lastCheck = inCheck; // check evasion does not count
6909                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6910                                 if(inCheck || lastCheck) count--; // check does not count
6911                                 i++;
6912                         }
6913                 }
6914                 if( count >= 100)
6915                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6916                          /* this is used to judge if draw claims are legal */
6917                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6918                          if(engineOpponent) {
6919                            SendToProgram("force\n", engineOpponent); // suppress reply
6920                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6921                          }
6922                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6923                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6924                          return 1;
6925                 }
6926
6927                 /* if draw offer is pending, treat it as a draw claim
6928                  * when draw condition present, to allow engines a way to
6929                  * claim draws before making their move to avoid a race
6930                  * condition occurring after their move
6931                  */
6932                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6933                          char *p = NULL;
6934                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6935                              p = "Draw claim: 50-move rule";
6936                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6937                              p = "Draw claim: 3-fold repetition";
6938                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6939                              p = "Draw claim: insufficient mating material";
6940                          if( p != NULL && canAdjudicate) {
6941                              if(engineOpponent) {
6942                                SendToProgram("force\n", engineOpponent); // suppress reply
6943                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6944                              }
6945                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6946                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6947                              return 1;
6948                          }
6949                 }
6950
6951                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6952                     if(engineOpponent) {
6953                       SendToProgram("force\n", engineOpponent); // suppress reply
6954                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6955                     }
6956                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6957                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6958                     return 1;
6959                 }
6960         return 0;
6961 }
6962
6963 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6964 {   // [HGM] book: this routine intercepts moves to simulate book replies
6965     char *bookHit = NULL;
6966
6967     //first determine if the incoming move brings opponent into his book
6968     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6969         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6970     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6971     if(bookHit != NULL && !cps->bookSuspend) {
6972         // make sure opponent is not going to reply after receiving move to book position
6973         SendToProgram("force\n", cps);
6974         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6975     }
6976     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6977     // now arrange restart after book miss
6978     if(bookHit) {
6979         // after a book hit we never send 'go', and the code after the call to this routine
6980         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6981         char buf[MSG_SIZ];
6982         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6983         SendToProgram(buf, cps);
6984         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6985     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6986         SendToProgram("go\n", cps);
6987         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6988     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6989         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6990             SendToProgram("go\n", cps); 
6991         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6992     }
6993     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6994 }
6995
6996 char *savedMessage;
6997 ChessProgramState *savedState;
6998 void DeferredBookMove(void)
6999 {
7000         if(savedState->lastPing != savedState->lastPong)
7001                     ScheduleDelayedEvent(DeferredBookMove, 10);
7002         else
7003         HandleMachineMove(savedMessage, savedState);
7004 }
7005
7006 void
7007 HandleMachineMove(message, cps)
7008      char *message;
7009      ChessProgramState *cps;
7010 {
7011     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7012     char realname[MSG_SIZ];
7013     int fromX, fromY, toX, toY;
7014     ChessMove moveType;
7015     char promoChar;
7016     char *p;
7017     int machineWhite;
7018     char *bookHit;
7019
7020     cps->userError = 0;
7021
7022 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7023     /*
7024      * Kludge to ignore BEL characters
7025      */
7026     while (*message == '\007') message++;
7027
7028     /*
7029      * [HGM] engine debug message: ignore lines starting with '#' character
7030      */
7031     if(cps->debug && *message == '#') return;
7032
7033     /*
7034      * Look for book output
7035      */
7036     if (cps == &first && bookRequested) {
7037         if (message[0] == '\t' || message[0] == ' ') {
7038             /* Part of the book output is here; append it */
7039             strcat(bookOutput, message);
7040             strcat(bookOutput, "  \n");
7041             return;
7042         } else if (bookOutput[0] != NULLCHAR) {
7043             /* All of book output has arrived; display it */
7044             char *p = bookOutput;
7045             while (*p != NULLCHAR) {
7046                 if (*p == '\t') *p = ' ';
7047                 p++;
7048             }
7049             DisplayInformation(bookOutput);
7050             bookRequested = FALSE;
7051             /* Fall through to parse the current output */
7052         }
7053     }
7054
7055     /*
7056      * Look for machine move.
7057      */
7058     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7059         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7060     {
7061         /* This method is only useful on engines that support ping */
7062         if (cps->lastPing != cps->lastPong) {
7063           if (gameMode == BeginningOfGame) {
7064             /* Extra move from before last new; ignore */
7065             if (appData.debugMode) {
7066                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7067             }
7068           } else {
7069             if (appData.debugMode) {
7070                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7071                         cps->which, gameMode);
7072             }
7073
7074             SendToProgram("undo\n", cps);
7075           }
7076           return;
7077         }
7078
7079         switch (gameMode) {
7080           case BeginningOfGame:
7081             /* Extra move from before last reset; ignore */
7082             if (appData.debugMode) {
7083                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7084             }
7085             return;
7086
7087           case EndOfGame:
7088           case IcsIdle:
7089           default:
7090             /* Extra move after we tried to stop.  The mode test is
7091                not a reliable way of detecting this problem, but it's
7092                the best we can do on engines that don't support ping.
7093             */
7094             if (appData.debugMode) {
7095                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7096                         cps->which, gameMode);
7097             }
7098             SendToProgram("undo\n", cps);
7099             return;
7100
7101           case MachinePlaysWhite:
7102           case IcsPlayingWhite:
7103             machineWhite = TRUE;
7104             break;
7105
7106           case MachinePlaysBlack:
7107           case IcsPlayingBlack:
7108             machineWhite = FALSE;
7109             break;
7110
7111           case TwoMachinesPlay:
7112             machineWhite = (cps->twoMachinesColor[0] == 'w');
7113             break;
7114         }
7115         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7116             if (appData.debugMode) {
7117                 fprintf(debugFP,
7118                         "Ignoring move out of turn by %s, gameMode %d"
7119                         ", forwardMost %d\n",
7120                         cps->which, gameMode, forwardMostMove);
7121             }
7122             return;
7123         }
7124
7125     if (appData.debugMode) { int f = forwardMostMove;
7126         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7127                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7128                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7129     }
7130         if(cps->alphaRank) AlphaRank(machineMove, 4);
7131         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7132                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7133             /* Machine move could not be parsed; ignore it. */
7134             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7135                     machineMove, cps->which);
7136             DisplayError(buf1, 0);
7137             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7138                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7139             if (gameMode == TwoMachinesPlay) {
7140               GameEnds(machineWhite ? BlackWins : WhiteWins,
7141                        buf1, GE_XBOARD);
7142             }
7143             return;
7144         }
7145
7146         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7147         /* So we have to redo legality test with true e.p. status here,  */
7148         /* to make sure an illegal e.p. capture does not slip through,   */
7149         /* to cause a forfeit on a justified illegal-move complaint      */
7150         /* of the opponent.                                              */
7151         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7152            ChessMove moveType;
7153            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7154                              fromY, fromX, toY, toX, promoChar);
7155             if (appData.debugMode) {
7156                 int i;
7157                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7158                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7159                 fprintf(debugFP, "castling rights\n");
7160             }
7161             if(moveType == IllegalMove) {
7162                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7163                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7164                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7165                            buf1, GE_XBOARD);
7166                 return;
7167            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7168            /* [HGM] Kludge to handle engines that send FRC-style castling
7169               when they shouldn't (like TSCP-Gothic) */
7170            switch(moveType) {
7171              case WhiteASideCastleFR:
7172              case BlackASideCastleFR:
7173                toX+=2;
7174                currentMoveString[2]++;
7175                break;
7176              case WhiteHSideCastleFR:
7177              case BlackHSideCastleFR:
7178                toX--;
7179                currentMoveString[2]--;
7180                break;
7181              default: ; // nothing to do, but suppresses warning of pedantic compilers
7182            }
7183         }
7184         hintRequested = FALSE;
7185         lastHint[0] = NULLCHAR;
7186         bookRequested = FALSE;
7187         /* Program may be pondering now */
7188         cps->maybeThinking = TRUE;
7189         if (cps->sendTime == 2) cps->sendTime = 1;
7190         if (cps->offeredDraw) cps->offeredDraw--;
7191
7192         /* currentMoveString is set as a side-effect of ParseOneMove */
7193         strcpy(machineMove, currentMoveString);
7194         strcat(machineMove, "\n");
7195         strcpy(moveList[forwardMostMove], machineMove);
7196
7197         /* [AS] Save move info*/
7198         pvInfoList[ forwardMostMove ].score = programStats.score;
7199         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7200         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7201
7202         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7203
7204         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7205         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7206             int count = 0;
7207
7208             while( count < adjudicateLossPlies ) {
7209                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7210
7211                 if( count & 1 ) {
7212                     score = -score; /* Flip score for winning side */
7213                 }
7214
7215                 if( score > adjudicateLossThreshold ) {
7216                     break;
7217                 }
7218
7219                 count++;
7220             }
7221
7222             if( count >= adjudicateLossPlies ) {
7223                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7224
7225                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7226                     "Xboard adjudication", 
7227                     GE_XBOARD );
7228
7229                 return;
7230             }
7231         }
7232
7233         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7234
7235 #if ZIPPY
7236         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7237             first.initDone) {
7238           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7239                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7240                 SendToICS("draw ");
7241                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7242           }
7243           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7244           ics_user_moved = 1;
7245           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7246                 char buf[3*MSG_SIZ];
7247
7248                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7249                         programStats.score / 100.,
7250                         programStats.depth,
7251                         programStats.time / 100.,
7252                         (unsigned int)programStats.nodes,
7253                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7254                         programStats.movelist);
7255                 SendToICS(buf);
7256 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7257           }
7258         }
7259 #endif
7260
7261         /* [AS] Clear stats for next move */
7262         ClearProgramStats();
7263         thinkOutput[0] = NULLCHAR;
7264         hiddenThinkOutputState = 0;
7265
7266         bookHit = NULL;
7267         if (gameMode == TwoMachinesPlay) {
7268             /* [HGM] relaying draw offers moved to after reception of move */
7269             /* and interpreting offer as claim if it brings draw condition */
7270             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7271                 SendToProgram("draw\n", cps->other);
7272             }
7273             if (cps->other->sendTime) {
7274                 SendTimeRemaining(cps->other,
7275                                   cps->other->twoMachinesColor[0] == 'w');
7276             }
7277             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7278             if (firstMove && !bookHit) {
7279                 firstMove = FALSE;
7280                 if (cps->other->useColors) {
7281                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7282                 }
7283                 SendToProgram("go\n", cps->other);
7284             }
7285             cps->other->maybeThinking = TRUE;
7286         }
7287
7288         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7289         
7290         if (!pausing && appData.ringBellAfterMoves) {
7291             RingBell();
7292         }
7293
7294         /* 
7295          * Reenable menu items that were disabled while
7296          * machine was thinking
7297          */
7298         if (gameMode != TwoMachinesPlay)
7299             SetUserThinkingEnables();
7300
7301         // [HGM] book: after book hit opponent has received move and is now in force mode
7302         // force the book reply into it, and then fake that it outputted this move by jumping
7303         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7304         if(bookHit) {
7305                 static char bookMove[MSG_SIZ]; // a bit generous?
7306
7307                 strcpy(bookMove, "move ");
7308                 strcat(bookMove, bookHit);
7309                 message = bookMove;
7310                 cps = cps->other;
7311                 programStats.nodes = programStats.depth = programStats.time = 
7312                 programStats.score = programStats.got_only_move = 0;
7313                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7314
7315                 if(cps->lastPing != cps->lastPong) {
7316                     savedMessage = message; // args for deferred call
7317                     savedState = cps;
7318                     ScheduleDelayedEvent(DeferredBookMove, 10);
7319                     return;
7320                 }
7321                 goto FakeBookMove;
7322         }
7323
7324         return;
7325     }
7326
7327     /* Set special modes for chess engines.  Later something general
7328      *  could be added here; for now there is just one kludge feature,
7329      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7330      *  when "xboard" is given as an interactive command.
7331      */
7332     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7333         cps->useSigint = FALSE;
7334         cps->useSigterm = FALSE;
7335     }
7336     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7337       ParseFeatures(message+8, cps);
7338       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7339     }
7340
7341     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7342      * want this, I was asked to put it in, and obliged.
7343      */
7344     if (!strncmp(message, "setboard ", 9)) {
7345         Board initial_position;
7346
7347         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7348
7349         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7350             DisplayError(_("Bad FEN received from engine"), 0);
7351             return ;
7352         } else {
7353            Reset(TRUE, FALSE);
7354            CopyBoard(boards[0], initial_position);
7355            initialRulePlies = FENrulePlies;
7356            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7357            else gameMode = MachinePlaysBlack;                 
7358            DrawPosition(FALSE, boards[currentMove]);
7359         }
7360         return;
7361     }
7362
7363     /*
7364      * Look for communication commands
7365      */
7366     if (!strncmp(message, "telluser ", 9)) {
7367         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7368         DisplayNote(message + 9);
7369         return;
7370     }
7371     if (!strncmp(message, "tellusererror ", 14)) {
7372         cps->userError = 1;
7373         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7374         DisplayError(message + 14, 0);
7375         return;
7376     }
7377     if (!strncmp(message, "tellopponent ", 13)) {
7378       if (appData.icsActive) {
7379         if (loggedOn) {
7380           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7381           SendToICS(buf1);
7382         }
7383       } else {
7384         DisplayNote(message + 13);
7385       }
7386       return;
7387     }
7388     if (!strncmp(message, "tellothers ", 11)) {
7389       if (appData.icsActive) {
7390         if (loggedOn) {
7391           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7392           SendToICS(buf1);
7393         }
7394       }
7395       return;
7396     }
7397     if (!strncmp(message, "tellall ", 8)) {
7398       if (appData.icsActive) {
7399         if (loggedOn) {
7400           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7401           SendToICS(buf1);
7402         }
7403       } else {
7404         DisplayNote(message + 8);
7405       }
7406       return;
7407     }
7408     if (strncmp(message, "warning", 7) == 0) {
7409         /* Undocumented feature, use tellusererror in new code */
7410         DisplayError(message, 0);
7411         return;
7412     }
7413     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7414         strcpy(realname, cps->tidy);
7415         strcat(realname, " query");
7416         AskQuestion(realname, buf2, buf1, cps->pr);
7417         return;
7418     }
7419     /* Commands from the engine directly to ICS.  We don't allow these to be 
7420      *  sent until we are logged on. Crafty kibitzes have been known to 
7421      *  interfere with the login process.
7422      */
7423     if (loggedOn) {
7424         if (!strncmp(message, "tellics ", 8)) {
7425             SendToICS(message + 8);
7426             SendToICS("\n");
7427             return;
7428         }
7429         if (!strncmp(message, "tellicsnoalias ", 15)) {
7430             SendToICS(ics_prefix);
7431             SendToICS(message + 15);
7432             SendToICS("\n");
7433             return;
7434         }
7435         /* The following are for backward compatibility only */
7436         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7437             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7438             SendToICS(ics_prefix);
7439             SendToICS(message);
7440             SendToICS("\n");
7441             return;
7442         }
7443     }
7444     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7445         return;
7446     }
7447     /*
7448      * If the move is illegal, cancel it and redraw the board.
7449      * Also deal with other error cases.  Matching is rather loose
7450      * here to accommodate engines written before the spec.
7451      */
7452     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7453         strncmp(message, "Error", 5) == 0) {
7454         if (StrStr(message, "name") || 
7455             StrStr(message, "rating") || StrStr(message, "?") ||
7456             StrStr(message, "result") || StrStr(message, "board") ||
7457             StrStr(message, "bk") || StrStr(message, "computer") ||
7458             StrStr(message, "variant") || StrStr(message, "hint") ||
7459             StrStr(message, "random") || StrStr(message, "depth") ||
7460             StrStr(message, "accepted")) {
7461             return;
7462         }
7463         if (StrStr(message, "protover")) {
7464           /* Program is responding to input, so it's apparently done
7465              initializing, and this error message indicates it is
7466              protocol version 1.  So we don't need to wait any longer
7467              for it to initialize and send feature commands. */
7468           FeatureDone(cps, 1);
7469           cps->protocolVersion = 1;
7470           return;
7471         }
7472         cps->maybeThinking = FALSE;
7473
7474         if (StrStr(message, "draw")) {
7475             /* Program doesn't have "draw" command */
7476             cps->sendDrawOffers = 0;
7477             return;
7478         }
7479         if (cps->sendTime != 1 &&
7480             (StrStr(message, "time") || StrStr(message, "otim"))) {
7481           /* Program apparently doesn't have "time" or "otim" command */
7482           cps->sendTime = 0;
7483           return;
7484         }
7485         if (StrStr(message, "analyze")) {
7486             cps->analysisSupport = FALSE;
7487             cps->analyzing = FALSE;
7488             Reset(FALSE, TRUE);
7489             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7490             DisplayError(buf2, 0);
7491             return;
7492         }
7493         if (StrStr(message, "(no matching move)st")) {
7494           /* Special kludge for GNU Chess 4 only */
7495           cps->stKludge = TRUE;
7496           SendTimeControl(cps, movesPerSession, timeControl,
7497                           timeIncrement, appData.searchDepth,
7498                           searchTime);
7499           return;
7500         }
7501         if (StrStr(message, "(no matching move)sd")) {
7502           /* Special kludge for GNU Chess 4 only */
7503           cps->sdKludge = TRUE;
7504           SendTimeControl(cps, movesPerSession, timeControl,
7505                           timeIncrement, appData.searchDepth,
7506                           searchTime);
7507           return;
7508         }
7509         if (!StrStr(message, "llegal")) {
7510             return;
7511         }
7512         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7513             gameMode == IcsIdle) return;
7514         if (forwardMostMove <= backwardMostMove) return;
7515         if (pausing) PauseEvent();
7516       if(appData.forceIllegal) {
7517             // [HGM] illegal: machine refused move; force position after move into it
7518           SendToProgram("force\n", cps);
7519           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7520                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7521                 // when black is to move, while there might be nothing on a2 or black
7522                 // might already have the move. So send the board as if white has the move.
7523                 // But first we must change the stm of the engine, as it refused the last move
7524                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7525                 if(WhiteOnMove(forwardMostMove)) {
7526                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7527                     SendBoard(cps, forwardMostMove); // kludgeless board
7528                 } else {
7529                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7530                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7531                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7532                 }
7533           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7534             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7535                  gameMode == TwoMachinesPlay)
7536               SendToProgram("go\n", cps);
7537             return;
7538       } else
7539         if (gameMode == PlayFromGameFile) {
7540             /* Stop reading this game file */
7541             gameMode = EditGame;
7542             ModeHighlight();
7543         }
7544         currentMove = forwardMostMove-1;
7545         DisplayMove(currentMove-1); /* before DisplayMoveError */
7546         SwitchClocks(forwardMostMove-1); // [HGM] race
7547         DisplayBothClocks();
7548         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7549                 parseList[currentMove], cps->which);
7550         DisplayMoveError(buf1);
7551         DrawPosition(FALSE, boards[currentMove]);
7552
7553         /* [HGM] illegal-move claim should forfeit game when Xboard */
7554         /* only passes fully legal moves                            */
7555         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7556             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7557                                 "False illegal-move claim", GE_XBOARD );
7558         }
7559         return;
7560     }
7561     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7562         /* Program has a broken "time" command that
7563            outputs a string not ending in newline.
7564            Don't use it. */
7565         cps->sendTime = 0;
7566     }
7567     
7568     /*
7569      * If chess program startup fails, exit with an error message.
7570      * Attempts to recover here are futile.
7571      */
7572     if ((StrStr(message, "unknown host") != NULL)
7573         || (StrStr(message, "No remote directory") != NULL)
7574         || (StrStr(message, "not found") != NULL)
7575         || (StrStr(message, "No such file") != NULL)
7576         || (StrStr(message, "can't alloc") != NULL)
7577         || (StrStr(message, "Permission denied") != NULL)) {
7578
7579         cps->maybeThinking = FALSE;
7580         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7581                 cps->which, cps->program, cps->host, message);
7582         RemoveInputSource(cps->isr);
7583         DisplayFatalError(buf1, 0, 1);
7584         return;
7585     }
7586     
7587     /* 
7588      * Look for hint output
7589      */
7590     if (sscanf(message, "Hint: %s", buf1) == 1) {
7591         if (cps == &first && hintRequested) {
7592             hintRequested = FALSE;
7593             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7594                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7595                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7596                                     PosFlags(forwardMostMove),
7597                                     fromY, fromX, toY, toX, promoChar, buf1);
7598                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7599                 DisplayInformation(buf2);
7600             } else {
7601                 /* Hint move could not be parsed!? */
7602               snprintf(buf2, sizeof(buf2),
7603                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7604                         buf1, cps->which);
7605                 DisplayError(buf2, 0);
7606             }
7607         } else {
7608             strcpy(lastHint, buf1);
7609         }
7610         return;
7611     }
7612
7613     /*
7614      * Ignore other messages if game is not in progress
7615      */
7616     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7617         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7618
7619     /*
7620      * look for win, lose, draw, or draw offer
7621      */
7622     if (strncmp(message, "1-0", 3) == 0) {
7623         char *p, *q, *r = "";
7624         p = strchr(message, '{');
7625         if (p) {
7626             q = strchr(p, '}');
7627             if (q) {
7628                 *q = NULLCHAR;
7629                 r = p + 1;
7630             }
7631         }
7632         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7633         return;
7634     } else if (strncmp(message, "0-1", 3) == 0) {
7635         char *p, *q, *r = "";
7636         p = strchr(message, '{');
7637         if (p) {
7638             q = strchr(p, '}');
7639             if (q) {
7640                 *q = NULLCHAR;
7641                 r = p + 1;
7642             }
7643         }
7644         /* Kludge for Arasan 4.1 bug */
7645         if (strcmp(r, "Black resigns") == 0) {
7646             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7647             return;
7648         }
7649         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7650         return;
7651     } else if (strncmp(message, "1/2", 3) == 0) {
7652         char *p, *q, *r = "";
7653         p = strchr(message, '{');
7654         if (p) {
7655             q = strchr(p, '}');
7656             if (q) {
7657                 *q = NULLCHAR;
7658                 r = p + 1;
7659             }
7660         }
7661             
7662         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7663         return;
7664
7665     } else if (strncmp(message, "White resign", 12) == 0) {
7666         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7667         return;
7668     } else if (strncmp(message, "Black resign", 12) == 0) {
7669         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7670         return;
7671     } else if (strncmp(message, "White matches", 13) == 0 ||
7672                strncmp(message, "Black matches", 13) == 0   ) {
7673         /* [HGM] ignore GNUShogi noises */
7674         return;
7675     } else if (strncmp(message, "White", 5) == 0 &&
7676                message[5] != '(' &&
7677                StrStr(message, "Black") == NULL) {
7678         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7679         return;
7680     } else if (strncmp(message, "Black", 5) == 0 &&
7681                message[5] != '(') {
7682         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7683         return;
7684     } else if (strcmp(message, "resign") == 0 ||
7685                strcmp(message, "computer resigns") == 0) {
7686         switch (gameMode) {
7687           case MachinePlaysBlack:
7688           case IcsPlayingBlack:
7689             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7690             break;
7691           case MachinePlaysWhite:
7692           case IcsPlayingWhite:
7693             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7694             break;
7695           case TwoMachinesPlay:
7696             if (cps->twoMachinesColor[0] == 'w')
7697               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7698             else
7699               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7700             break;
7701           default:
7702             /* can't happen */
7703             break;
7704         }
7705         return;
7706     } else if (strncmp(message, "opponent mates", 14) == 0) {
7707         switch (gameMode) {
7708           case MachinePlaysBlack:
7709           case IcsPlayingBlack:
7710             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7711             break;
7712           case MachinePlaysWhite:
7713           case IcsPlayingWhite:
7714             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7715             break;
7716           case TwoMachinesPlay:
7717             if (cps->twoMachinesColor[0] == 'w')
7718               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7719             else
7720               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7721             break;
7722           default:
7723             /* can't happen */
7724             break;
7725         }
7726         return;
7727     } else if (strncmp(message, "computer mates", 14) == 0) {
7728         switch (gameMode) {
7729           case MachinePlaysBlack:
7730           case IcsPlayingBlack:
7731             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7732             break;
7733           case MachinePlaysWhite:
7734           case IcsPlayingWhite:
7735             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7736             break;
7737           case TwoMachinesPlay:
7738             if (cps->twoMachinesColor[0] == 'w')
7739               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7740             else
7741               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7742             break;
7743           default:
7744             /* can't happen */
7745             break;
7746         }
7747         return;
7748     } else if (strncmp(message, "checkmate", 9) == 0) {
7749         if (WhiteOnMove(forwardMostMove)) {
7750             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7751         } else {
7752             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7753         }
7754         return;
7755     } else if (strstr(message, "Draw") != NULL ||
7756                strstr(message, "game is a draw") != NULL) {
7757         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7758         return;
7759     } else if (strstr(message, "offer") != NULL &&
7760                strstr(message, "draw") != NULL) {
7761 #if ZIPPY
7762         if (appData.zippyPlay && first.initDone) {
7763             /* Relay offer to ICS */
7764             SendToICS(ics_prefix);
7765             SendToICS("draw\n");
7766         }
7767 #endif
7768         cps->offeredDraw = 2; /* valid until this engine moves twice */
7769         if (gameMode == TwoMachinesPlay) {
7770             if (cps->other->offeredDraw) {
7771                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7772             /* [HGM] in two-machine mode we delay relaying draw offer      */
7773             /* until after we also have move, to see if it is really claim */
7774             }
7775         } else if (gameMode == MachinePlaysWhite ||
7776                    gameMode == MachinePlaysBlack) {
7777           if (userOfferedDraw) {
7778             DisplayInformation(_("Machine accepts your draw offer"));
7779             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7780           } else {
7781             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7782           }
7783         }
7784     }
7785
7786     
7787     /*
7788      * Look for thinking output
7789      */
7790     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7791           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7792                                 ) {
7793         int plylev, mvleft, mvtot, curscore, time;
7794         char mvname[MOVE_LEN];
7795         u64 nodes; // [DM]
7796         char plyext;
7797         int ignore = FALSE;
7798         int prefixHint = FALSE;
7799         mvname[0] = NULLCHAR;
7800
7801         switch (gameMode) {
7802           case MachinePlaysBlack:
7803           case IcsPlayingBlack:
7804             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7805             break;
7806           case MachinePlaysWhite:
7807           case IcsPlayingWhite:
7808             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7809             break;
7810           case AnalyzeMode:
7811           case AnalyzeFile:
7812             break;
7813           case IcsObserving: /* [DM] icsEngineAnalyze */
7814             if (!appData.icsEngineAnalyze) ignore = TRUE;
7815             break;
7816           case TwoMachinesPlay:
7817             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7818                 ignore = TRUE;
7819             }
7820             break;
7821           default:
7822             ignore = TRUE;
7823             break;
7824         }
7825
7826         if (!ignore) {
7827             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7828             buf1[0] = NULLCHAR;
7829             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7830                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7831
7832                 if (plyext != ' ' && plyext != '\t') {
7833                     time *= 100;
7834                 }
7835
7836                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7837                 if( cps->scoreIsAbsolute && 
7838                     ( gameMode == MachinePlaysBlack ||
7839                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7840                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7841                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7842                      !WhiteOnMove(currentMove)
7843                     ) )
7844                 {
7845                     curscore = -curscore;
7846                 }
7847
7848
7849                 tempStats.depth = plylev;
7850                 tempStats.nodes = nodes;
7851                 tempStats.time = time;
7852                 tempStats.score = curscore;
7853                 tempStats.got_only_move = 0;
7854
7855                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7856                         int ticklen;
7857
7858                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7859                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7860                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7861                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7862                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7863                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7864                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7865                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7866                 }
7867
7868                 /* Buffer overflow protection */
7869                 if (buf1[0] != NULLCHAR) {
7870                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7871                         && appData.debugMode) {
7872                         fprintf(debugFP,
7873                                 "PV is too long; using the first %u bytes.\n",
7874                                 (unsigned) sizeof(tempStats.movelist) - 1);
7875                     }
7876
7877                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7878                 } else {
7879                     sprintf(tempStats.movelist, " no PV\n");
7880                 }
7881
7882                 if (tempStats.seen_stat) {
7883                     tempStats.ok_to_send = 1;
7884                 }
7885
7886                 if (strchr(tempStats.movelist, '(') != NULL) {
7887                     tempStats.line_is_book = 1;
7888                     tempStats.nr_moves = 0;
7889                     tempStats.moves_left = 0;
7890                 } else {
7891                     tempStats.line_is_book = 0;
7892                 }
7893
7894                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7895                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7896
7897                 SendProgramStatsToFrontend( cps, &tempStats );
7898
7899                 /* 
7900                     [AS] Protect the thinkOutput buffer from overflow... this
7901                     is only useful if buf1 hasn't overflowed first!
7902                 */
7903                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7904                         plylev, 
7905                         (gameMode == TwoMachinesPlay ?
7906                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7907                         ((double) curscore) / 100.0,
7908                         prefixHint ? lastHint : "",
7909                         prefixHint ? " " : "" );
7910
7911                 if( buf1[0] != NULLCHAR ) {
7912                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7913
7914                     if( strlen(buf1) > max_len ) {
7915                         if( appData.debugMode) {
7916                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7917                         }
7918                         buf1[max_len+1] = '\0';
7919                     }
7920
7921                     strcat( thinkOutput, buf1 );
7922                 }
7923
7924                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7925                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7926                     DisplayMove(currentMove - 1);
7927                 }
7928                 return;
7929
7930             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7931                 /* crafty (9.25+) says "(only move) <move>"
7932                  * if there is only 1 legal move
7933                  */
7934                 sscanf(p, "(only move) %s", buf1);
7935                 sprintf(thinkOutput, "%s (only move)", buf1);
7936                 sprintf(programStats.movelist, "%s (only move)", buf1);
7937                 programStats.depth = 1;
7938                 programStats.nr_moves = 1;
7939                 programStats.moves_left = 1;
7940                 programStats.nodes = 1;
7941                 programStats.time = 1;
7942                 programStats.got_only_move = 1;
7943
7944                 /* Not really, but we also use this member to
7945                    mean "line isn't going to change" (Crafty
7946                    isn't searching, so stats won't change) */
7947                 programStats.line_is_book = 1;
7948
7949                 SendProgramStatsToFrontend( cps, &programStats );
7950                 
7951                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7952                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7953                     DisplayMove(currentMove - 1);
7954                 }
7955                 return;
7956             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7957                               &time, &nodes, &plylev, &mvleft,
7958                               &mvtot, mvname) >= 5) {
7959                 /* The stat01: line is from Crafty (9.29+) in response
7960                    to the "." command */
7961                 programStats.seen_stat = 1;
7962                 cps->maybeThinking = TRUE;
7963
7964                 if (programStats.got_only_move || !appData.periodicUpdates)
7965                   return;
7966
7967                 programStats.depth = plylev;
7968                 programStats.time = time;
7969                 programStats.nodes = nodes;
7970                 programStats.moves_left = mvleft;
7971                 programStats.nr_moves = mvtot;
7972                 strcpy(programStats.move_name, mvname);
7973                 programStats.ok_to_send = 1;
7974                 programStats.movelist[0] = '\0';
7975
7976                 SendProgramStatsToFrontend( cps, &programStats );
7977
7978                 return;
7979
7980             } else if (strncmp(message,"++",2) == 0) {
7981                 /* Crafty 9.29+ outputs this */
7982                 programStats.got_fail = 2;
7983                 return;
7984
7985             } else if (strncmp(message,"--",2) == 0) {
7986                 /* Crafty 9.29+ outputs this */
7987                 programStats.got_fail = 1;
7988                 return;
7989
7990             } else if (thinkOutput[0] != NULLCHAR &&
7991                        strncmp(message, "    ", 4) == 0) {
7992                 unsigned message_len;
7993
7994                 p = message;
7995                 while (*p && *p == ' ') p++;
7996
7997                 message_len = strlen( p );
7998
7999                 /* [AS] Avoid buffer overflow */
8000                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8001                     strcat(thinkOutput, " ");
8002                     strcat(thinkOutput, p);
8003                 }
8004
8005                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8006                     strcat(programStats.movelist, " ");
8007                     strcat(programStats.movelist, p);
8008                 }
8009
8010                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8011                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8012                     DisplayMove(currentMove - 1);
8013                 }
8014                 return;
8015             }
8016         }
8017         else {
8018             buf1[0] = NULLCHAR;
8019
8020             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8021                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8022             {
8023                 ChessProgramStats cpstats;
8024
8025                 if (plyext != ' ' && plyext != '\t') {
8026                     time *= 100;
8027                 }
8028
8029                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8030                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8031                     curscore = -curscore;
8032                 }
8033
8034                 cpstats.depth = plylev;
8035                 cpstats.nodes = nodes;
8036                 cpstats.time = time;
8037                 cpstats.score = curscore;
8038                 cpstats.got_only_move = 0;
8039                 cpstats.movelist[0] = '\0';
8040
8041                 if (buf1[0] != NULLCHAR) {
8042                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8043                 }
8044
8045                 cpstats.ok_to_send = 0;
8046                 cpstats.line_is_book = 0;
8047                 cpstats.nr_moves = 0;
8048                 cpstats.moves_left = 0;
8049
8050                 SendProgramStatsToFrontend( cps, &cpstats );
8051             }
8052         }
8053     }
8054 }
8055
8056
8057 /* Parse a game score from the character string "game", and
8058    record it as the history of the current game.  The game
8059    score is NOT assumed to start from the standard position. 
8060    The display is not updated in any way.
8061    */
8062 void
8063 ParseGameHistory(game)
8064      char *game;
8065 {
8066     ChessMove moveType;
8067     int fromX, fromY, toX, toY, boardIndex;
8068     char promoChar;
8069     char *p, *q;
8070     char buf[MSG_SIZ];
8071
8072     if (appData.debugMode)
8073       fprintf(debugFP, "Parsing game history: %s\n", game);
8074
8075     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8076     gameInfo.site = StrSave(appData.icsHost);
8077     gameInfo.date = PGNDate();
8078     gameInfo.round = StrSave("-");
8079
8080     /* Parse out names of players */
8081     while (*game == ' ') game++;
8082     p = buf;
8083     while (*game != ' ') *p++ = *game++;
8084     *p = NULLCHAR;
8085     gameInfo.white = StrSave(buf);
8086     while (*game == ' ') game++;
8087     p = buf;
8088     while (*game != ' ' && *game != '\n') *p++ = *game++;
8089     *p = NULLCHAR;
8090     gameInfo.black = StrSave(buf);
8091
8092     /* Parse moves */
8093     boardIndex = blackPlaysFirst ? 1 : 0;
8094     yynewstr(game);
8095     for (;;) {
8096         yyboardindex = boardIndex;
8097         moveType = (ChessMove) yylex();
8098         switch (moveType) {
8099           case IllegalMove:             /* maybe suicide chess, etc. */
8100   if (appData.debugMode) {
8101     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8102     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8103     setbuf(debugFP, NULL);
8104   }
8105           case WhitePromotion:
8106           case BlackPromotion:
8107           case WhiteNonPromotion:
8108           case BlackNonPromotion:
8109           case NormalMove:
8110           case WhiteCapturesEnPassant:
8111           case BlackCapturesEnPassant:
8112           case WhiteKingSideCastle:
8113           case WhiteQueenSideCastle:
8114           case BlackKingSideCastle:
8115           case BlackQueenSideCastle:
8116           case WhiteKingSideCastleWild:
8117           case WhiteQueenSideCastleWild:
8118           case BlackKingSideCastleWild:
8119           case BlackQueenSideCastleWild:
8120           /* PUSH Fabien */
8121           case WhiteHSideCastleFR:
8122           case WhiteASideCastleFR:
8123           case BlackHSideCastleFR:
8124           case BlackASideCastleFR:
8125           /* POP Fabien */
8126             fromX = currentMoveString[0] - AAA;
8127             fromY = currentMoveString[1] - ONE;
8128             toX = currentMoveString[2] - AAA;
8129             toY = currentMoveString[3] - ONE;
8130             promoChar = currentMoveString[4];
8131             break;
8132           case WhiteDrop:
8133           case BlackDrop:
8134             fromX = moveType == WhiteDrop ?
8135               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8136             (int) CharToPiece(ToLower(currentMoveString[0]));
8137             fromY = DROP_RANK;
8138             toX = currentMoveString[2] - AAA;
8139             toY = currentMoveString[3] - ONE;
8140             promoChar = NULLCHAR;
8141             break;
8142           case AmbiguousMove:
8143             /* bug? */
8144             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8145   if (appData.debugMode) {
8146     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8147     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8148     setbuf(debugFP, NULL);
8149   }
8150             DisplayError(buf, 0);
8151             return;
8152           case ImpossibleMove:
8153             /* bug? */
8154             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8155   if (appData.debugMode) {
8156     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8157     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8158     setbuf(debugFP, NULL);
8159   }
8160             DisplayError(buf, 0);
8161             return;
8162           case (ChessMove) 0:   /* end of file */
8163             if (boardIndex < backwardMostMove) {
8164                 /* Oops, gap.  How did that happen? */
8165                 DisplayError(_("Gap in move list"), 0);
8166                 return;
8167             }
8168             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8169             if (boardIndex > forwardMostMove) {
8170                 forwardMostMove = boardIndex;
8171             }
8172             return;
8173           case ElapsedTime:
8174             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8175                 strcat(parseList[boardIndex-1], " ");
8176                 strcat(parseList[boardIndex-1], yy_text);
8177             }
8178             continue;
8179           case Comment:
8180           case PGNTag:
8181           case NAG:
8182           default:
8183             /* ignore */
8184             continue;
8185           case WhiteWins:
8186           case BlackWins:
8187           case GameIsDrawn:
8188           case GameUnfinished:
8189             if (gameMode == IcsExamining) {
8190                 if (boardIndex < backwardMostMove) {
8191                     /* Oops, gap.  How did that happen? */
8192                     return;
8193                 }
8194                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8195                 return;
8196             }
8197             gameInfo.result = moveType;
8198             p = strchr(yy_text, '{');
8199             if (p == NULL) p = strchr(yy_text, '(');
8200             if (p == NULL) {
8201                 p = yy_text;
8202                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8203             } else {
8204                 q = strchr(p, *p == '{' ? '}' : ')');
8205                 if (q != NULL) *q = NULLCHAR;
8206                 p++;
8207             }
8208             gameInfo.resultDetails = StrSave(p);
8209             continue;
8210         }
8211         if (boardIndex >= forwardMostMove &&
8212             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8213             backwardMostMove = blackPlaysFirst ? 1 : 0;
8214             return;
8215         }
8216         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8217                                  fromY, fromX, toY, toX, promoChar,
8218                                  parseList[boardIndex]);
8219         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8220         /* currentMoveString is set as a side-effect of yylex */
8221         strcpy(moveList[boardIndex], currentMoveString);
8222         strcat(moveList[boardIndex], "\n");
8223         boardIndex++;
8224         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8225         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8226           case MT_NONE:
8227           case MT_STALEMATE:
8228           default:
8229             break;
8230           case MT_CHECK:
8231             if(gameInfo.variant != VariantShogi)
8232                 strcat(parseList[boardIndex - 1], "+");
8233             break;
8234           case MT_CHECKMATE:
8235           case MT_STAINMATE:
8236             strcat(parseList[boardIndex - 1], "#");
8237             break;
8238         }
8239     }
8240 }
8241
8242
8243 /* Apply a move to the given board  */
8244 void
8245 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8246      int fromX, fromY, toX, toY;
8247      int promoChar;
8248      Board board;
8249 {
8250   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8251   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8252
8253     /* [HGM] compute & store e.p. status and castling rights for new position */
8254     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8255
8256       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8257       oldEP = (signed char)board[EP_STATUS];
8258       board[EP_STATUS] = EP_NONE;
8259
8260       if( board[toY][toX] != EmptySquare ) 
8261            board[EP_STATUS] = EP_CAPTURE;  
8262
8263   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8264   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8265        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8266          
8267   if (fromY == DROP_RANK) {
8268         /* must be first */
8269         piece = board[toY][toX] = (ChessSquare) fromX;
8270   } else {
8271       int i;
8272
8273       if( board[fromY][fromX] == WhitePawn ) {
8274            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8275                board[EP_STATUS] = EP_PAWN_MOVE;
8276            if( toY-fromY==2) {
8277                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8278                         gameInfo.variant != VariantBerolina || toX < fromX)
8279                       board[EP_STATUS] = toX | berolina;
8280                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8281                         gameInfo.variant != VariantBerolina || toX > fromX) 
8282                       board[EP_STATUS] = toX;
8283            }
8284       } else 
8285       if( board[fromY][fromX] == BlackPawn ) {
8286            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8287                board[EP_STATUS] = EP_PAWN_MOVE; 
8288            if( toY-fromY== -2) {
8289                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8290                         gameInfo.variant != VariantBerolina || toX < fromX)
8291                       board[EP_STATUS] = toX | berolina;
8292                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8293                         gameInfo.variant != VariantBerolina || toX > fromX) 
8294                       board[EP_STATUS] = toX;
8295            }
8296        }
8297
8298        for(i=0; i<nrCastlingRights; i++) {
8299            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8300               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8301              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8302        }
8303
8304      if (fromX == toX && fromY == toY) return;
8305
8306      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8307      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8308      if(gameInfo.variant == VariantKnightmate)
8309          king += (int) WhiteUnicorn - (int) WhiteKing;
8310
8311     /* Code added by Tord: */
8312     /* FRC castling assumed when king captures friendly rook. */
8313     if (board[fromY][fromX] == WhiteKing &&
8314              board[toY][toX] == WhiteRook) {
8315       board[fromY][fromX] = EmptySquare;
8316       board[toY][toX] = EmptySquare;
8317       if(toX > fromX) {
8318         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8319       } else {
8320         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8321       }
8322     } else if (board[fromY][fromX] == BlackKing &&
8323                board[toY][toX] == BlackRook) {
8324       board[fromY][fromX] = EmptySquare;
8325       board[toY][toX] = EmptySquare;
8326       if(toX > fromX) {
8327         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8328       } else {
8329         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8330       }
8331     /* End of code added by Tord */
8332
8333     } else if (board[fromY][fromX] == king
8334         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8335         && toY == fromY && toX > fromX+1) {
8336         board[fromY][fromX] = EmptySquare;
8337         board[toY][toX] = king;
8338         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8339         board[fromY][BOARD_RGHT-1] = EmptySquare;
8340     } else if (board[fromY][fromX] == king
8341         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8342                && toY == fromY && toX < fromX-1) {
8343         board[fromY][fromX] = EmptySquare;
8344         board[toY][toX] = king;
8345         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8346         board[fromY][BOARD_LEFT] = EmptySquare;
8347     } else if (board[fromY][fromX] == WhitePawn
8348                && toY >= BOARD_HEIGHT-promoRank
8349                && gameInfo.variant != VariantXiangqi
8350                ) {
8351         /* white pawn promotion */
8352         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8353         if (board[toY][toX] == EmptySquare) {
8354             board[toY][toX] = WhiteQueen;
8355         }
8356         if(gameInfo.variant==VariantBughouse ||
8357            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8358             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8359         board[fromY][fromX] = EmptySquare;
8360     } else if ((fromY == BOARD_HEIGHT-4)
8361                && (toX != fromX)
8362                && gameInfo.variant != VariantXiangqi
8363                && gameInfo.variant != VariantBerolina
8364                && (board[fromY][fromX] == WhitePawn)
8365                && (board[toY][toX] == EmptySquare)) {
8366         board[fromY][fromX] = EmptySquare;
8367         board[toY][toX] = WhitePawn;
8368         captured = board[toY - 1][toX];
8369         board[toY - 1][toX] = EmptySquare;
8370     } else if ((fromY == BOARD_HEIGHT-4)
8371                && (toX == fromX)
8372                && gameInfo.variant == VariantBerolina
8373                && (board[fromY][fromX] == WhitePawn)
8374                && (board[toY][toX] == EmptySquare)) {
8375         board[fromY][fromX] = EmptySquare;
8376         board[toY][toX] = WhitePawn;
8377         if(oldEP & EP_BEROLIN_A) {
8378                 captured = board[fromY][fromX-1];
8379                 board[fromY][fromX-1] = EmptySquare;
8380         }else{  captured = board[fromY][fromX+1];
8381                 board[fromY][fromX+1] = EmptySquare;
8382         }
8383     } else if (board[fromY][fromX] == king
8384         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8385                && toY == fromY && toX > fromX+1) {
8386         board[fromY][fromX] = EmptySquare;
8387         board[toY][toX] = king;
8388         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8389         board[fromY][BOARD_RGHT-1] = EmptySquare;
8390     } else if (board[fromY][fromX] == king
8391         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8392                && toY == fromY && toX < fromX-1) {
8393         board[fromY][fromX] = EmptySquare;
8394         board[toY][toX] = king;
8395         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8396         board[fromY][BOARD_LEFT] = EmptySquare;
8397     } else if (fromY == 7 && fromX == 3
8398                && board[fromY][fromX] == BlackKing
8399                && toY == 7 && toX == 5) {
8400         board[fromY][fromX] = EmptySquare;
8401         board[toY][toX] = BlackKing;
8402         board[fromY][7] = EmptySquare;
8403         board[toY][4] = BlackRook;
8404     } else if (fromY == 7 && fromX == 3
8405                && board[fromY][fromX] == BlackKing
8406                && toY == 7 && toX == 1) {
8407         board[fromY][fromX] = EmptySquare;
8408         board[toY][toX] = BlackKing;
8409         board[fromY][0] = EmptySquare;
8410         board[toY][2] = BlackRook;
8411     } else if (board[fromY][fromX] == BlackPawn
8412                && toY < promoRank
8413                && gameInfo.variant != VariantXiangqi
8414                ) {
8415         /* black pawn promotion */
8416         board[toY][toX] = CharToPiece(ToLower(promoChar));
8417         if (board[toY][toX] == EmptySquare) {
8418             board[toY][toX] = BlackQueen;
8419         }
8420         if(gameInfo.variant==VariantBughouse ||
8421            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8422             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8423         board[fromY][fromX] = EmptySquare;
8424     } else if ((fromY == 3)
8425                && (toX != fromX)
8426                && gameInfo.variant != VariantXiangqi
8427                && gameInfo.variant != VariantBerolina
8428                && (board[fromY][fromX] == BlackPawn)
8429                && (board[toY][toX] == EmptySquare)) {
8430         board[fromY][fromX] = EmptySquare;
8431         board[toY][toX] = BlackPawn;
8432         captured = board[toY + 1][toX];
8433         board[toY + 1][toX] = EmptySquare;
8434     } else if ((fromY == 3)
8435                && (toX == fromX)
8436                && gameInfo.variant == VariantBerolina
8437                && (board[fromY][fromX] == BlackPawn)
8438                && (board[toY][toX] == EmptySquare)) {
8439         board[fromY][fromX] = EmptySquare;
8440         board[toY][toX] = BlackPawn;
8441         if(oldEP & EP_BEROLIN_A) {
8442                 captured = board[fromY][fromX-1];
8443                 board[fromY][fromX-1] = EmptySquare;
8444         }else{  captured = board[fromY][fromX+1];
8445                 board[fromY][fromX+1] = EmptySquare;
8446         }
8447     } else {
8448         board[toY][toX] = board[fromY][fromX];
8449         board[fromY][fromX] = EmptySquare;
8450     }
8451   }
8452
8453     if (gameInfo.holdingsWidth != 0) {
8454
8455       /* !!A lot more code needs to be written to support holdings  */
8456       /* [HGM] OK, so I have written it. Holdings are stored in the */
8457       /* penultimate board files, so they are automaticlly stored   */
8458       /* in the game history.                                       */
8459       if (fromY == DROP_RANK) {
8460         /* Delete from holdings, by decreasing count */
8461         /* and erasing image if necessary            */
8462         p = (int) fromX;
8463         if(p < (int) BlackPawn) { /* white drop */
8464              p -= (int)WhitePawn;
8465                  p = PieceToNumber((ChessSquare)p);
8466              if(p >= gameInfo.holdingsSize) p = 0;
8467              if(--board[p][BOARD_WIDTH-2] <= 0)
8468                   board[p][BOARD_WIDTH-1] = EmptySquare;
8469              if((int)board[p][BOARD_WIDTH-2] < 0)
8470                         board[p][BOARD_WIDTH-2] = 0;
8471         } else {                  /* black drop */
8472              p -= (int)BlackPawn;
8473                  p = PieceToNumber((ChessSquare)p);
8474              if(p >= gameInfo.holdingsSize) p = 0;
8475              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8476                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8477              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8478                         board[BOARD_HEIGHT-1-p][1] = 0;
8479         }
8480       }
8481       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8482           && gameInfo.variant != VariantBughouse        ) {
8483         /* [HGM] holdings: Add to holdings, if holdings exist */
8484         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8485                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8486                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8487         }
8488         p = (int) captured;
8489         if (p >= (int) BlackPawn) {
8490           p -= (int)BlackPawn;
8491           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8492                   /* in Shogi restore piece to its original  first */
8493                   captured = (ChessSquare) (DEMOTED captured);
8494                   p = DEMOTED p;
8495           }
8496           p = PieceToNumber((ChessSquare)p);
8497           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8498           board[p][BOARD_WIDTH-2]++;
8499           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8500         } else {
8501           p -= (int)WhitePawn;
8502           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8503                   captured = (ChessSquare) (DEMOTED captured);
8504                   p = DEMOTED p;
8505           }
8506           p = PieceToNumber((ChessSquare)p);
8507           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8508           board[BOARD_HEIGHT-1-p][1]++;
8509           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8510         }
8511       }
8512     } else if (gameInfo.variant == VariantAtomic) {
8513       if (captured != EmptySquare) {
8514         int y, x;
8515         for (y = toY-1; y <= toY+1; y++) {
8516           for (x = toX-1; x <= toX+1; x++) {
8517             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8518                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8519               board[y][x] = EmptySquare;
8520             }
8521           }
8522         }
8523         board[toY][toX] = EmptySquare;
8524       }
8525     }
8526     if(promoChar == '+') {
8527         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8528         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8529     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8530         board[toY][toX] = CharToPiece(promoChar);
8531     }
8532     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8533                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8534         // [HGM] superchess: take promotion piece out of holdings
8535         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8536         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8537             if(!--board[k][BOARD_WIDTH-2])
8538                 board[k][BOARD_WIDTH-1] = EmptySquare;
8539         } else {
8540             if(!--board[BOARD_HEIGHT-1-k][1])
8541                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8542         }
8543     }
8544
8545 }
8546
8547 /* Updates forwardMostMove */
8548 void
8549 MakeMove(fromX, fromY, toX, toY, promoChar)
8550      int fromX, fromY, toX, toY;
8551      int promoChar;
8552 {
8553 //    forwardMostMove++; // [HGM] bare: moved downstream
8554
8555     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8556         int timeLeft; static int lastLoadFlag=0; int king, piece;
8557         piece = boards[forwardMostMove][fromY][fromX];
8558         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8559         if(gameInfo.variant == VariantKnightmate)
8560             king += (int) WhiteUnicorn - (int) WhiteKing;
8561         if(forwardMostMove == 0) {
8562             if(blackPlaysFirst) 
8563                 fprintf(serverMoves, "%s;", second.tidy);
8564             fprintf(serverMoves, "%s;", first.tidy);
8565             if(!blackPlaysFirst) 
8566                 fprintf(serverMoves, "%s;", second.tidy);
8567         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8568         lastLoadFlag = loadFlag;
8569         // print base move
8570         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8571         // print castling suffix
8572         if( toY == fromY && piece == king ) {
8573             if(toX-fromX > 1)
8574                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8575             if(fromX-toX >1)
8576                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8577         }
8578         // e.p. suffix
8579         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8580              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8581              boards[forwardMostMove][toY][toX] == EmptySquare
8582              && fromX != toX && fromY != toY)
8583                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8584         // promotion suffix
8585         if(promoChar != NULLCHAR)
8586                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8587         if(!loadFlag) {
8588             fprintf(serverMoves, "/%d/%d",
8589                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8590             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8591             else                      timeLeft = blackTimeRemaining/1000;
8592             fprintf(serverMoves, "/%d", timeLeft);
8593         }
8594         fflush(serverMoves);
8595     }
8596
8597     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8598       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8599                         0, 1);
8600       return;
8601     }
8602     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8603     if (commentList[forwardMostMove+1] != NULL) {
8604         free(commentList[forwardMostMove+1]);
8605         commentList[forwardMostMove+1] = NULL;
8606     }
8607     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8608     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8609     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8610     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8611     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8612     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8613     gameInfo.result = GameUnfinished;
8614     if (gameInfo.resultDetails != NULL) {
8615         free(gameInfo.resultDetails);
8616         gameInfo.resultDetails = NULL;
8617     }
8618     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8619                               moveList[forwardMostMove - 1]);
8620     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8621                              PosFlags(forwardMostMove - 1),
8622                              fromY, fromX, toY, toX, promoChar,
8623                              parseList[forwardMostMove - 1]);
8624     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8625       case MT_NONE:
8626       case MT_STALEMATE:
8627       default:
8628         break;
8629       case MT_CHECK:
8630         if(gameInfo.variant != VariantShogi)
8631             strcat(parseList[forwardMostMove - 1], "+");
8632         break;
8633       case MT_CHECKMATE:
8634       case MT_STAINMATE:
8635         strcat(parseList[forwardMostMove - 1], "#");
8636         break;
8637     }
8638     if (appData.debugMode) {
8639         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8640     }
8641
8642 }
8643
8644 /* Updates currentMove if not pausing */
8645 void
8646 ShowMove(fromX, fromY, toX, toY)
8647 {
8648     int instant = (gameMode == PlayFromGameFile) ?
8649         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8650     if(appData.noGUI) return;
8651     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8652         if (!instant) {
8653             if (forwardMostMove == currentMove + 1) {
8654                 AnimateMove(boards[forwardMostMove - 1],
8655                             fromX, fromY, toX, toY);
8656             }
8657             if (appData.highlightLastMove) {
8658                 SetHighlights(fromX, fromY, toX, toY);
8659             }
8660         }
8661         currentMove = forwardMostMove;
8662     }
8663
8664     if (instant) return;
8665
8666     DisplayMove(currentMove - 1);
8667     DrawPosition(FALSE, boards[currentMove]);
8668     DisplayBothClocks();
8669     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8670 }
8671
8672 void SendEgtPath(ChessProgramState *cps)
8673 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8674         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8675
8676         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8677
8678         while(*p) {
8679             char c, *q = name+1, *r, *s;
8680
8681             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8682             while(*p && *p != ',') *q++ = *p++;
8683             *q++ = ':'; *q = 0;
8684             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8685                 strcmp(name, ",nalimov:") == 0 ) {
8686                 // take nalimov path from the menu-changeable option first, if it is defined
8687                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8688                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8689             } else
8690             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8691                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8692                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8693                 s = r = StrStr(s, ":") + 1; // beginning of path info
8694                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8695                 c = *r; *r = 0;             // temporarily null-terminate path info
8696                     *--q = 0;               // strip of trailig ':' from name
8697                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8698                 *r = c;
8699                 SendToProgram(buf,cps);     // send egtbpath command for this format
8700             }
8701             if(*p == ',') p++; // read away comma to position for next format name
8702         }
8703 }
8704
8705 void
8706 InitChessProgram(cps, setup)
8707      ChessProgramState *cps;
8708      int setup; /* [HGM] needed to setup FRC opening position */
8709 {
8710     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8711     if (appData.noChessProgram) return;
8712     hintRequested = FALSE;
8713     bookRequested = FALSE;
8714
8715     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8716     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8717     if(cps->memSize) { /* [HGM] memory */
8718         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8719         SendToProgram(buf, cps);
8720     }
8721     SendEgtPath(cps); /* [HGM] EGT */
8722     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8723         sprintf(buf, "cores %d\n", appData.smpCores);
8724         SendToProgram(buf, cps);
8725     }
8726
8727     SendToProgram(cps->initString, cps);
8728     if (gameInfo.variant != VariantNormal &&
8729         gameInfo.variant != VariantLoadable
8730         /* [HGM] also send variant if board size non-standard */
8731         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8732                                             ) {
8733       char *v = VariantName(gameInfo.variant);
8734       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8735         /* [HGM] in protocol 1 we have to assume all variants valid */
8736         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8737         DisplayFatalError(buf, 0, 1);
8738         return;
8739       }
8740
8741       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8742       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8743       if( gameInfo.variant == VariantXiangqi )
8744            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8745       if( gameInfo.variant == VariantShogi )
8746            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8747       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8748            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8749       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8750                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8751            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8752       if( gameInfo.variant == VariantCourier )
8753            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8754       if( gameInfo.variant == VariantSuper )
8755            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8756       if( gameInfo.variant == VariantGreat )
8757            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8758
8759       if(overruled) {
8760            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8761                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8762            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8763            if(StrStr(cps->variants, b) == NULL) { 
8764                // specific sized variant not known, check if general sizing allowed
8765                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8766                    if(StrStr(cps->variants, "boardsize") == NULL) {
8767                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8768                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8769                        DisplayFatalError(buf, 0, 1);
8770                        return;
8771                    }
8772                    /* [HGM] here we really should compare with the maximum supported board size */
8773                }
8774            }
8775       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8776       sprintf(buf, "variant %s\n", b);
8777       SendToProgram(buf, cps);
8778     }
8779     currentlyInitializedVariant = gameInfo.variant;
8780
8781     /* [HGM] send opening position in FRC to first engine */
8782     if(setup) {
8783           SendToProgram("force\n", cps);
8784           SendBoard(cps, 0);
8785           /* engine is now in force mode! Set flag to wake it up after first move. */
8786           setboardSpoiledMachineBlack = 1;
8787     }
8788
8789     if (cps->sendICS) {
8790       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8791       SendToProgram(buf, cps);
8792     }
8793     cps->maybeThinking = FALSE;
8794     cps->offeredDraw = 0;
8795     if (!appData.icsActive) {
8796         SendTimeControl(cps, movesPerSession, timeControl,
8797                         timeIncrement, appData.searchDepth,
8798                         searchTime);
8799     }
8800     if (appData.showThinking 
8801         // [HGM] thinking: four options require thinking output to be sent
8802         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8803                                 ) {
8804         SendToProgram("post\n", cps);
8805     }
8806     SendToProgram("hard\n", cps);
8807     if (!appData.ponderNextMove) {
8808         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8809            it without being sure what state we are in first.  "hard"
8810            is not a toggle, so that one is OK.
8811          */
8812         SendToProgram("easy\n", cps);
8813     }
8814     if (cps->usePing) {
8815       sprintf(buf, "ping %d\n", ++cps->lastPing);
8816       SendToProgram(buf, cps);
8817     }
8818     cps->initDone = TRUE;
8819 }   
8820
8821
8822 void
8823 StartChessProgram(cps)
8824      ChessProgramState *cps;
8825 {
8826     char buf[MSG_SIZ];
8827     int err;
8828
8829     if (appData.noChessProgram) return;
8830     cps->initDone = FALSE;
8831
8832     if (strcmp(cps->host, "localhost") == 0) {
8833         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8834     } else if (*appData.remoteShell == NULLCHAR) {
8835         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8836     } else {
8837         if (*appData.remoteUser == NULLCHAR) {
8838           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8839                     cps->program);
8840         } else {
8841           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8842                     cps->host, appData.remoteUser, cps->program);
8843         }
8844         err = StartChildProcess(buf, "", &cps->pr);
8845     }
8846     
8847     if (err != 0) {
8848         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8849         DisplayFatalError(buf, err, 1);
8850         cps->pr = NoProc;
8851         cps->isr = NULL;
8852         return;
8853     }
8854     
8855     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8856     if (cps->protocolVersion > 1) {
8857       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8858       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8859       cps->comboCnt = 0;  //                and values of combo boxes
8860       SendToProgram(buf, cps);
8861     } else {
8862       SendToProgram("xboard\n", cps);
8863     }
8864 }
8865
8866
8867 void
8868 TwoMachinesEventIfReady P((void))
8869 {
8870   if (first.lastPing != first.lastPong) {
8871     DisplayMessage("", _("Waiting for first chess program"));
8872     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8873     return;
8874   }
8875   if (second.lastPing != second.lastPong) {
8876     DisplayMessage("", _("Waiting for second chess program"));
8877     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8878     return;
8879   }
8880   ThawUI();
8881   TwoMachinesEvent();
8882 }
8883
8884 void
8885 NextMatchGame P((void))
8886 {
8887     int index; /* [HGM] autoinc: step load index during match */
8888     Reset(FALSE, TRUE);
8889     if (*appData.loadGameFile != NULLCHAR) {
8890         index = appData.loadGameIndex;
8891         if(index < 0) { // [HGM] autoinc
8892             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8893             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8894         } 
8895         LoadGameFromFile(appData.loadGameFile,
8896                          index,
8897                          appData.loadGameFile, FALSE);
8898     } else if (*appData.loadPositionFile != NULLCHAR) {
8899         index = appData.loadPositionIndex;
8900         if(index < 0) { // [HGM] autoinc
8901             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8902             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8903         } 
8904         LoadPositionFromFile(appData.loadPositionFile,
8905                              index,
8906                              appData.loadPositionFile);
8907     }
8908     TwoMachinesEventIfReady();
8909 }
8910
8911 void UserAdjudicationEvent( int result )
8912 {
8913     ChessMove gameResult = GameIsDrawn;
8914
8915     if( result > 0 ) {
8916         gameResult = WhiteWins;
8917     }
8918     else if( result < 0 ) {
8919         gameResult = BlackWins;
8920     }
8921
8922     if( gameMode == TwoMachinesPlay ) {
8923         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8924     }
8925 }
8926
8927
8928 // [HGM] save: calculate checksum of game to make games easily identifiable
8929 int StringCheckSum(char *s)
8930 {
8931         int i = 0;
8932         if(s==NULL) return 0;
8933         while(*s) i = i*259 + *s++;
8934         return i;
8935 }
8936
8937 int GameCheckSum()
8938 {
8939         int i, sum=0;
8940         for(i=backwardMostMove; i<forwardMostMove; i++) {
8941                 sum += pvInfoList[i].depth;
8942                 sum += StringCheckSum(parseList[i]);
8943                 sum += StringCheckSum(commentList[i]);
8944                 sum *= 261;
8945         }
8946         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8947         return sum + StringCheckSum(commentList[i]);
8948 } // end of save patch
8949
8950 void
8951 GameEnds(result, resultDetails, whosays)
8952      ChessMove result;
8953      char *resultDetails;
8954      int whosays;
8955 {
8956     GameMode nextGameMode;
8957     int isIcsGame;
8958     char buf[MSG_SIZ], popupRequested = 0;
8959
8960     if(endingGame) return; /* [HGM] crash: forbid recursion */
8961     endingGame = 1;
8962     if(twoBoards) { // [HGM] dual: switch back to one board
8963         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8964         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8965     }
8966     if (appData.debugMode) {
8967       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8968               result, resultDetails ? resultDetails : "(null)", whosays);
8969     }
8970
8971     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8972
8973     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8974         /* If we are playing on ICS, the server decides when the
8975            game is over, but the engine can offer to draw, claim 
8976            a draw, or resign. 
8977          */
8978 #if ZIPPY
8979         if (appData.zippyPlay && first.initDone) {
8980             if (result == GameIsDrawn) {
8981                 /* In case draw still needs to be claimed */
8982                 SendToICS(ics_prefix);
8983                 SendToICS("draw\n");
8984             } else if (StrCaseStr(resultDetails, "resign")) {
8985                 SendToICS(ics_prefix);
8986                 SendToICS("resign\n");
8987             }
8988         }
8989 #endif
8990         endingGame = 0; /* [HGM] crash */
8991         return;
8992     }
8993
8994     /* If we're loading the game from a file, stop */
8995     if (whosays == GE_FILE) {
8996       (void) StopLoadGameTimer();
8997       gameFileFP = NULL;
8998     }
8999
9000     /* Cancel draw offers */
9001     first.offeredDraw = second.offeredDraw = 0;
9002
9003     /* If this is an ICS game, only ICS can really say it's done;
9004        if not, anyone can. */
9005     isIcsGame = (gameMode == IcsPlayingWhite || 
9006                  gameMode == IcsPlayingBlack || 
9007                  gameMode == IcsObserving    || 
9008                  gameMode == IcsExamining);
9009
9010     if (!isIcsGame || whosays == GE_ICS) {
9011         /* OK -- not an ICS game, or ICS said it was done */
9012         StopClocks();
9013         if (!isIcsGame && !appData.noChessProgram) 
9014           SetUserThinkingEnables();
9015     
9016         /* [HGM] if a machine claims the game end we verify this claim */
9017         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9018             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9019                 char claimer;
9020                 ChessMove trueResult = (ChessMove) -1;
9021
9022                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9023                                             first.twoMachinesColor[0] :
9024                                             second.twoMachinesColor[0] ;
9025
9026                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9027                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9028                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9029                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9030                 } else
9031                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9032                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9033                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9034                 } else
9035                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9036                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9037                 }
9038
9039                 // now verify win claims, but not in drop games, as we don't understand those yet
9040                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9041                                                  || gameInfo.variant == VariantGreat) &&
9042                     (result == WhiteWins && claimer == 'w' ||
9043                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9044                       if (appData.debugMode) {
9045                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9046                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9047                       }
9048                       if(result != trueResult) {
9049                               sprintf(buf, "False win claim: '%s'", resultDetails);
9050                               result = claimer == 'w' ? BlackWins : WhiteWins;
9051                               resultDetails = buf;
9052                       }
9053                 } else
9054                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9055                     && (forwardMostMove <= backwardMostMove ||
9056                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9057                         (claimer=='b')==(forwardMostMove&1))
9058                                                                                   ) {
9059                       /* [HGM] verify: draws that were not flagged are false claims */
9060                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9061                       result = claimer == 'w' ? BlackWins : WhiteWins;
9062                       resultDetails = buf;
9063                 }
9064                 /* (Claiming a loss is accepted no questions asked!) */
9065             }
9066             /* [HGM] bare: don't allow bare King to win */
9067             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9068                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9069                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9070                && result != GameIsDrawn)
9071             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9072                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9073                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9074                         if(p >= 0 && p <= (int)WhiteKing) k++;
9075                 }
9076                 if (appData.debugMode) {
9077                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9078                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9079                 }
9080                 if(k <= 1) {
9081                         result = GameIsDrawn;
9082                         sprintf(buf, "%s but bare king", resultDetails);
9083                         resultDetails = buf;
9084                 }
9085             }
9086         }
9087
9088
9089         if(serverMoves != NULL && !loadFlag) { char c = '=';
9090             if(result==WhiteWins) c = '+';
9091             if(result==BlackWins) c = '-';
9092             if(resultDetails != NULL)
9093                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9094         }
9095         if (resultDetails != NULL) {
9096             gameInfo.result = result;
9097             gameInfo.resultDetails = StrSave(resultDetails);
9098
9099             /* display last move only if game was not loaded from file */
9100             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9101                 DisplayMove(currentMove - 1);
9102     
9103             if (forwardMostMove != 0) {
9104                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9105                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9106                                                                 ) {
9107                     if (*appData.saveGameFile != NULLCHAR) {
9108                         SaveGameToFile(appData.saveGameFile, TRUE);
9109                     } else if (appData.autoSaveGames) {
9110                         AutoSaveGame();
9111                     }
9112                     if (*appData.savePositionFile != NULLCHAR) {
9113                         SavePositionToFile(appData.savePositionFile);
9114                     }
9115                 }
9116             }
9117
9118             /* Tell program how game ended in case it is learning */
9119             /* [HGM] Moved this to after saving the PGN, just in case */
9120             /* engine died and we got here through time loss. In that */
9121             /* case we will get a fatal error writing the pipe, which */
9122             /* would otherwise lose us the PGN.                       */
9123             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9124             /* output during GameEnds should never be fatal anymore   */
9125             if (gameMode == MachinePlaysWhite ||
9126                 gameMode == MachinePlaysBlack ||
9127                 gameMode == TwoMachinesPlay ||
9128                 gameMode == IcsPlayingWhite ||
9129                 gameMode == IcsPlayingBlack ||
9130                 gameMode == BeginningOfGame) {
9131                 char buf[MSG_SIZ];
9132                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9133                         resultDetails);
9134                 if (first.pr != NoProc) {
9135                     SendToProgram(buf, &first);
9136                 }
9137                 if (second.pr != NoProc &&
9138                     gameMode == TwoMachinesPlay) {
9139                     SendToProgram(buf, &second);
9140                 }
9141             }
9142         }
9143
9144         if (appData.icsActive) {
9145             if (appData.quietPlay &&
9146                 (gameMode == IcsPlayingWhite ||
9147                  gameMode == IcsPlayingBlack)) {
9148                 SendToICS(ics_prefix);
9149                 SendToICS("set shout 1\n");
9150             }
9151             nextGameMode = IcsIdle;
9152             ics_user_moved = FALSE;
9153             /* clean up premove.  It's ugly when the game has ended and the
9154              * premove highlights are still on the board.
9155              */
9156             if (gotPremove) {
9157               gotPremove = FALSE;
9158               ClearPremoveHighlights();
9159               DrawPosition(FALSE, boards[currentMove]);
9160             }
9161             if (whosays == GE_ICS) {
9162                 switch (result) {
9163                 case WhiteWins:
9164                     if (gameMode == IcsPlayingWhite)
9165                         PlayIcsWinSound();
9166                     else if(gameMode == IcsPlayingBlack)
9167                         PlayIcsLossSound();
9168                     break;
9169                 case BlackWins:
9170                     if (gameMode == IcsPlayingBlack)
9171                         PlayIcsWinSound();
9172                     else if(gameMode == IcsPlayingWhite)
9173                         PlayIcsLossSound();
9174                     break;
9175                 case GameIsDrawn:
9176                     PlayIcsDrawSound();
9177                     break;
9178                 default:
9179                     PlayIcsUnfinishedSound();
9180                 }
9181             }
9182         } else if (gameMode == EditGame ||
9183                    gameMode == PlayFromGameFile || 
9184                    gameMode == AnalyzeMode || 
9185                    gameMode == AnalyzeFile) {
9186             nextGameMode = gameMode;
9187         } else {
9188             nextGameMode = EndOfGame;
9189         }
9190         pausing = FALSE;
9191         ModeHighlight();
9192     } else {
9193         nextGameMode = gameMode;
9194     }
9195
9196     if (appData.noChessProgram) {
9197         gameMode = nextGameMode;
9198         ModeHighlight();
9199         endingGame = 0; /* [HGM] crash */
9200         return;
9201     }
9202
9203     if (first.reuse) {
9204         /* Put first chess program into idle state */
9205         if (first.pr != NoProc &&
9206             (gameMode == MachinePlaysWhite ||
9207              gameMode == MachinePlaysBlack ||
9208              gameMode == TwoMachinesPlay ||
9209              gameMode == IcsPlayingWhite ||
9210              gameMode == IcsPlayingBlack ||
9211              gameMode == BeginningOfGame)) {
9212             SendToProgram("force\n", &first);
9213             if (first.usePing) {
9214               char buf[MSG_SIZ];
9215               sprintf(buf, "ping %d\n", ++first.lastPing);
9216               SendToProgram(buf, &first);
9217             }
9218         }
9219     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9220         /* Kill off first chess program */
9221         if (first.isr != NULL)
9222           RemoveInputSource(first.isr);
9223         first.isr = NULL;
9224     
9225         if (first.pr != NoProc) {
9226             ExitAnalyzeMode();
9227             DoSleep( appData.delayBeforeQuit );
9228             SendToProgram("quit\n", &first);
9229             DoSleep( appData.delayAfterQuit );
9230             DestroyChildProcess(first.pr, first.useSigterm);
9231         }
9232         first.pr = NoProc;
9233     }
9234     if (second.reuse) {
9235         /* Put second chess program into idle state */
9236         if (second.pr != NoProc &&
9237             gameMode == TwoMachinesPlay) {
9238             SendToProgram("force\n", &second);
9239             if (second.usePing) {
9240               char buf[MSG_SIZ];
9241               sprintf(buf, "ping %d\n", ++second.lastPing);
9242               SendToProgram(buf, &second);
9243             }
9244         }
9245     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9246         /* Kill off second chess program */
9247         if (second.isr != NULL)
9248           RemoveInputSource(second.isr);
9249         second.isr = NULL;
9250     
9251         if (second.pr != NoProc) {
9252             DoSleep( appData.delayBeforeQuit );
9253             SendToProgram("quit\n", &second);
9254             DoSleep( appData.delayAfterQuit );
9255             DestroyChildProcess(second.pr, second.useSigterm);
9256         }
9257         second.pr = NoProc;
9258     }
9259
9260     if (matchMode && gameMode == TwoMachinesPlay) {
9261         switch (result) {
9262         case WhiteWins:
9263           if (first.twoMachinesColor[0] == 'w') {
9264             first.matchWins++;
9265           } else {
9266             second.matchWins++;
9267           }
9268           break;
9269         case BlackWins:
9270           if (first.twoMachinesColor[0] == 'b') {
9271             first.matchWins++;
9272           } else {
9273             second.matchWins++;
9274           }
9275           break;
9276         default:
9277           break;
9278         }
9279         if (matchGame < appData.matchGames) {
9280             char *tmp;
9281             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9282                 tmp = first.twoMachinesColor;
9283                 first.twoMachinesColor = second.twoMachinesColor;
9284                 second.twoMachinesColor = tmp;
9285             }
9286             gameMode = nextGameMode;
9287             matchGame++;
9288             if(appData.matchPause>10000 || appData.matchPause<10)
9289                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9290             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9291             endingGame = 0; /* [HGM] crash */
9292             return;
9293         } else {
9294             gameMode = nextGameMode;
9295             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9296                     first.tidy, second.tidy,
9297                     first.matchWins, second.matchWins,
9298                     appData.matchGames - (first.matchWins + second.matchWins));
9299             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9300         }
9301     }
9302     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9303         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9304       ExitAnalyzeMode();
9305     gameMode = nextGameMode;
9306     ModeHighlight();
9307     endingGame = 0;  /* [HGM] crash */
9308     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9309       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9310         matchMode = FALSE; appData.matchGames = matchGame = 0;
9311         DisplayNote(buf);
9312       }
9313     }
9314 }
9315
9316 /* Assumes program was just initialized (initString sent).
9317    Leaves program in force mode. */
9318 void
9319 FeedMovesToProgram(cps, upto) 
9320      ChessProgramState *cps;
9321      int upto;
9322 {
9323     int i;
9324     
9325     if (appData.debugMode)
9326       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9327               startedFromSetupPosition ? "position and " : "",
9328               backwardMostMove, upto, cps->which);
9329     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9330         // [HGM] variantswitch: make engine aware of new variant
9331         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9332                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9333         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9334         SendToProgram(buf, cps);
9335         currentlyInitializedVariant = gameInfo.variant;
9336     }
9337     SendToProgram("force\n", cps);
9338     if (startedFromSetupPosition) {
9339         SendBoard(cps, backwardMostMove);
9340     if (appData.debugMode) {
9341         fprintf(debugFP, "feedMoves\n");
9342     }
9343     }
9344     for (i = backwardMostMove; i < upto; i++) {
9345         SendMoveToProgram(i, cps);
9346     }
9347 }
9348
9349
9350 void
9351 ResurrectChessProgram()
9352 {
9353      /* The chess program may have exited.
9354         If so, restart it and feed it all the moves made so far. */
9355
9356     if (appData.noChessProgram || first.pr != NoProc) return;
9357     
9358     StartChessProgram(&first);
9359     InitChessProgram(&first, FALSE);
9360     FeedMovesToProgram(&first, currentMove);
9361
9362     if (!first.sendTime) {
9363         /* can't tell gnuchess what its clock should read,
9364            so we bow to its notion. */
9365         ResetClocks();
9366         timeRemaining[0][currentMove] = whiteTimeRemaining;
9367         timeRemaining[1][currentMove] = blackTimeRemaining;
9368     }
9369
9370     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9371                 appData.icsEngineAnalyze) && first.analysisSupport) {
9372       SendToProgram("analyze\n", &first);
9373       first.analyzing = TRUE;
9374     }
9375 }
9376
9377 /*
9378  * Button procedures
9379  */
9380 void
9381 Reset(redraw, init)
9382      int redraw, init;
9383 {
9384     int i;
9385
9386     if (appData.debugMode) {
9387         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9388                 redraw, init, gameMode);
9389     }
9390     CleanupTail(); // [HGM] vari: delete any stored variations
9391     pausing = pauseExamInvalid = FALSE;
9392     startedFromSetupPosition = blackPlaysFirst = FALSE;
9393     firstMove = TRUE;
9394     whiteFlag = blackFlag = FALSE;
9395     userOfferedDraw = FALSE;
9396     hintRequested = bookRequested = FALSE;
9397     first.maybeThinking = FALSE;
9398     second.maybeThinking = FALSE;
9399     first.bookSuspend = FALSE; // [HGM] book
9400     second.bookSuspend = FALSE;
9401     thinkOutput[0] = NULLCHAR;
9402     lastHint[0] = NULLCHAR;
9403     ClearGameInfo(&gameInfo);
9404     gameInfo.variant = StringToVariant(appData.variant);
9405     ics_user_moved = ics_clock_paused = FALSE;
9406     ics_getting_history = H_FALSE;
9407     ics_gamenum = -1;
9408     white_holding[0] = black_holding[0] = NULLCHAR;
9409     ClearProgramStats();
9410     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9411     
9412     ResetFrontEnd();
9413     ClearHighlights();
9414     flipView = appData.flipView;
9415     ClearPremoveHighlights();
9416     gotPremove = FALSE;
9417     alarmSounded = FALSE;
9418
9419     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9420     if(appData.serverMovesName != NULL) {
9421         /* [HGM] prepare to make moves file for broadcasting */
9422         clock_t t = clock();
9423         if(serverMoves != NULL) fclose(serverMoves);
9424         serverMoves = fopen(appData.serverMovesName, "r");
9425         if(serverMoves != NULL) {
9426             fclose(serverMoves);
9427             /* delay 15 sec before overwriting, so all clients can see end */
9428             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9429         }
9430         serverMoves = fopen(appData.serverMovesName, "w");
9431     }
9432
9433     ExitAnalyzeMode();
9434     gameMode = BeginningOfGame;
9435     ModeHighlight();
9436     if(appData.icsActive) gameInfo.variant = VariantNormal;
9437     currentMove = forwardMostMove = backwardMostMove = 0;
9438     InitPosition(redraw);
9439     for (i = 0; i < MAX_MOVES; i++) {
9440         if (commentList[i] != NULL) {
9441             free(commentList[i]);
9442             commentList[i] = NULL;
9443         }
9444     }
9445     ResetClocks();
9446     timeRemaining[0][0] = whiteTimeRemaining;
9447     timeRemaining[1][0] = blackTimeRemaining;
9448     if (first.pr == NULL) {
9449         StartChessProgram(&first);
9450     }
9451     if (init) {
9452             InitChessProgram(&first, startedFromSetupPosition);
9453     }
9454     DisplayTitle("");
9455     DisplayMessage("", "");
9456     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9457     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9458 }
9459
9460 void
9461 AutoPlayGameLoop()
9462 {
9463     for (;;) {
9464         if (!AutoPlayOneMove())
9465           return;
9466         if (matchMode || appData.timeDelay == 0)
9467           continue;
9468         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9469           return;
9470         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9471         break;
9472     }
9473 }
9474
9475
9476 int
9477 AutoPlayOneMove()
9478 {
9479     int fromX, fromY, toX, toY;
9480
9481     if (appData.debugMode) {
9482       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9483     }
9484
9485     if (gameMode != PlayFromGameFile)
9486       return FALSE;
9487
9488     if (currentMove >= forwardMostMove) {
9489       gameMode = EditGame;
9490       ModeHighlight();
9491
9492       /* [AS] Clear current move marker at the end of a game */
9493       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9494
9495       return FALSE;
9496     }
9497     
9498     toX = moveList[currentMove][2] - AAA;
9499     toY = moveList[currentMove][3] - ONE;
9500
9501     if (moveList[currentMove][1] == '@') {
9502         if (appData.highlightLastMove) {
9503             SetHighlights(-1, -1, toX, toY);
9504         }
9505     } else {
9506         fromX = moveList[currentMove][0] - AAA;
9507         fromY = moveList[currentMove][1] - ONE;
9508
9509         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9510
9511         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9512
9513         if (appData.highlightLastMove) {
9514             SetHighlights(fromX, fromY, toX, toY);
9515         }
9516     }
9517     DisplayMove(currentMove);
9518     SendMoveToProgram(currentMove++, &first);
9519     DisplayBothClocks();
9520     DrawPosition(FALSE, boards[currentMove]);
9521     // [HGM] PV info: always display, routine tests if empty
9522     DisplayComment(currentMove - 1, commentList[currentMove]);
9523     return TRUE;
9524 }
9525
9526
9527 int
9528 LoadGameOneMove(readAhead)
9529      ChessMove readAhead;
9530 {
9531     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9532     char promoChar = NULLCHAR;
9533     ChessMove moveType;
9534     char move[MSG_SIZ];
9535     char *p, *q;
9536     
9537     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9538         gameMode != AnalyzeMode && gameMode != Training) {
9539         gameFileFP = NULL;
9540         return FALSE;
9541     }
9542     
9543     yyboardindex = forwardMostMove;
9544     if (readAhead != (ChessMove)0) {
9545       moveType = readAhead;
9546     } else {
9547       if (gameFileFP == NULL)
9548           return FALSE;
9549       moveType = (ChessMove) yylex();
9550     }
9551     
9552     done = FALSE;
9553     switch (moveType) {
9554       case Comment:
9555         if (appData.debugMode) 
9556           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9557         p = yy_text;
9558
9559         /* append the comment but don't display it */
9560         AppendComment(currentMove, p, FALSE);
9561         return TRUE;
9562
9563       case WhiteCapturesEnPassant:
9564       case BlackCapturesEnPassant:
9565       case WhitePromotion:
9566       case BlackPromotion:
9567       case WhiteNonPromotion:
9568       case BlackNonPromotion:
9569       case NormalMove:
9570       case WhiteKingSideCastle:
9571       case WhiteQueenSideCastle:
9572       case BlackKingSideCastle:
9573       case BlackQueenSideCastle:
9574       case WhiteKingSideCastleWild:
9575       case WhiteQueenSideCastleWild:
9576       case BlackKingSideCastleWild:
9577       case BlackQueenSideCastleWild:
9578       /* PUSH Fabien */
9579       case WhiteHSideCastleFR:
9580       case WhiteASideCastleFR:
9581       case BlackHSideCastleFR:
9582       case BlackASideCastleFR:
9583       /* POP Fabien */
9584         if (appData.debugMode)
9585           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9586         fromX = currentMoveString[0] - AAA;
9587         fromY = currentMoveString[1] - ONE;
9588         toX = currentMoveString[2] - AAA;
9589         toY = currentMoveString[3] - ONE;
9590         promoChar = currentMoveString[4];
9591         break;
9592
9593       case WhiteDrop:
9594       case BlackDrop:
9595         if (appData.debugMode)
9596           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9597         fromX = moveType == WhiteDrop ?
9598           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9599         (int) CharToPiece(ToLower(currentMoveString[0]));
9600         fromY = DROP_RANK;
9601         toX = currentMoveString[2] - AAA;
9602         toY = currentMoveString[3] - ONE;
9603         break;
9604
9605       case WhiteWins:
9606       case BlackWins:
9607       case GameIsDrawn:
9608       case GameUnfinished:
9609         if (appData.debugMode)
9610           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9611         p = strchr(yy_text, '{');
9612         if (p == NULL) p = strchr(yy_text, '(');
9613         if (p == NULL) {
9614             p = yy_text;
9615             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9616         } else {
9617             q = strchr(p, *p == '{' ? '}' : ')');
9618             if (q != NULL) *q = NULLCHAR;
9619             p++;
9620         }
9621         GameEnds(moveType, p, GE_FILE);
9622         done = TRUE;
9623         if (cmailMsgLoaded) {
9624             ClearHighlights();
9625             flipView = WhiteOnMove(currentMove);
9626             if (moveType == GameUnfinished) flipView = !flipView;
9627             if (appData.debugMode)
9628               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9629         }
9630         break;
9631
9632       case (ChessMove) 0:       /* end of file */
9633         if (appData.debugMode)
9634           fprintf(debugFP, "Parser hit end of file\n");
9635         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9636           case MT_NONE:
9637           case MT_CHECK:
9638             break;
9639           case MT_CHECKMATE:
9640           case MT_STAINMATE:
9641             if (WhiteOnMove(currentMove)) {
9642                 GameEnds(BlackWins, "Black mates", GE_FILE);
9643             } else {
9644                 GameEnds(WhiteWins, "White mates", GE_FILE);
9645             }
9646             break;
9647           case MT_STALEMATE:
9648             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9649             break;
9650         }
9651         done = TRUE;
9652         break;
9653
9654       case MoveNumberOne:
9655         if (lastLoadGameStart == GNUChessGame) {
9656             /* GNUChessGames have numbers, but they aren't move numbers */
9657             if (appData.debugMode)
9658               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9659                       yy_text, (int) moveType);
9660             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9661         }
9662         /* else fall thru */
9663
9664       case XBoardGame:
9665       case GNUChessGame:
9666       case PGNTag:
9667         /* Reached start of next game in file */
9668         if (appData.debugMode)
9669           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9670         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9671           case MT_NONE:
9672           case MT_CHECK:
9673             break;
9674           case MT_CHECKMATE:
9675           case MT_STAINMATE:
9676             if (WhiteOnMove(currentMove)) {
9677                 GameEnds(BlackWins, "Black mates", GE_FILE);
9678             } else {
9679                 GameEnds(WhiteWins, "White mates", GE_FILE);
9680             }
9681             break;
9682           case MT_STALEMATE:
9683             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9684             break;
9685         }
9686         done = TRUE;
9687         break;
9688
9689       case PositionDiagram:     /* should not happen; ignore */
9690       case ElapsedTime:         /* ignore */
9691       case NAG:                 /* ignore */
9692         if (appData.debugMode)
9693           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9694                   yy_text, (int) moveType);
9695         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9696
9697       case IllegalMove:
9698         if (appData.testLegality) {
9699             if (appData.debugMode)
9700               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9701             sprintf(move, _("Illegal move: %d.%s%s"),
9702                     (forwardMostMove / 2) + 1,
9703                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9704             DisplayError(move, 0);
9705             done = TRUE;
9706         } else {
9707             if (appData.debugMode)
9708               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9709                       yy_text, currentMoveString);
9710             fromX = currentMoveString[0] - AAA;
9711             fromY = currentMoveString[1] - ONE;
9712             toX = currentMoveString[2] - AAA;
9713             toY = currentMoveString[3] - ONE;
9714             promoChar = currentMoveString[4];
9715         }
9716         break;
9717
9718       case AmbiguousMove:
9719         if (appData.debugMode)
9720           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9721         sprintf(move, _("Ambiguous move: %d.%s%s"),
9722                 (forwardMostMove / 2) + 1,
9723                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9724         DisplayError(move, 0);
9725         done = TRUE;
9726         break;
9727
9728       default:
9729       case ImpossibleMove:
9730         if (appData.debugMode)
9731           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9732         sprintf(move, _("Illegal move: %d.%s%s"),
9733                 (forwardMostMove / 2) + 1,
9734                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9735         DisplayError(move, 0);
9736         done = TRUE;
9737         break;
9738     }
9739
9740     if (done) {
9741         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9742             DrawPosition(FALSE, boards[currentMove]);
9743             DisplayBothClocks();
9744             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9745               DisplayComment(currentMove - 1, commentList[currentMove]);
9746         }
9747         (void) StopLoadGameTimer();
9748         gameFileFP = NULL;
9749         cmailOldMove = forwardMostMove;
9750         return FALSE;
9751     } else {
9752         /* currentMoveString is set as a side-effect of yylex */
9753         strcat(currentMoveString, "\n");
9754         strcpy(moveList[forwardMostMove], currentMoveString);
9755         
9756         thinkOutput[0] = NULLCHAR;
9757         MakeMove(fromX, fromY, toX, toY, promoChar);
9758         currentMove = forwardMostMove;
9759         return TRUE;
9760     }
9761 }
9762
9763 /* Load the nth game from the given file */
9764 int
9765 LoadGameFromFile(filename, n, title, useList)
9766      char *filename;
9767      int n;
9768      char *title;
9769      /*Boolean*/ int useList;
9770 {
9771     FILE *f;
9772     char buf[MSG_SIZ];
9773
9774     if (strcmp(filename, "-") == 0) {
9775         f = stdin;
9776         title = "stdin";
9777     } else {
9778         f = fopen(filename, "rb");
9779         if (f == NULL) {
9780           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9781             DisplayError(buf, errno);
9782             return FALSE;
9783         }
9784     }
9785     if (fseek(f, 0, 0) == -1) {
9786         /* f is not seekable; probably a pipe */
9787         useList = FALSE;
9788     }
9789     if (useList && n == 0) {
9790         int error = GameListBuild(f);
9791         if (error) {
9792             DisplayError(_("Cannot build game list"), error);
9793         } else if (!ListEmpty(&gameList) &&
9794                    ((ListGame *) gameList.tailPred)->number > 1) {
9795             GameListPopUp(f, title);
9796             return TRUE;
9797         }
9798         GameListDestroy();
9799         n = 1;
9800     }
9801     if (n == 0) n = 1;
9802     return LoadGame(f, n, title, FALSE);
9803 }
9804
9805
9806 void
9807 MakeRegisteredMove()
9808 {
9809     int fromX, fromY, toX, toY;
9810     char promoChar;
9811     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9812         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9813           case CMAIL_MOVE:
9814           case CMAIL_DRAW:
9815             if (appData.debugMode)
9816               fprintf(debugFP, "Restoring %s for game %d\n",
9817                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9818     
9819             thinkOutput[0] = NULLCHAR;
9820             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9821             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9822             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9823             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9824             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9825             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9826             MakeMove(fromX, fromY, toX, toY, promoChar);
9827             ShowMove(fromX, fromY, toX, toY);
9828               
9829             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9830               case MT_NONE:
9831               case MT_CHECK:
9832                 break;
9833                 
9834               case MT_CHECKMATE:
9835               case MT_STAINMATE:
9836                 if (WhiteOnMove(currentMove)) {
9837                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9838                 } else {
9839                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9840                 }
9841                 break;
9842                 
9843               case MT_STALEMATE:
9844                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9845                 break;
9846             }
9847
9848             break;
9849             
9850           case CMAIL_RESIGN:
9851             if (WhiteOnMove(currentMove)) {
9852                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9853             } else {
9854                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9855             }
9856             break;
9857             
9858           case CMAIL_ACCEPT:
9859             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9860             break;
9861               
9862           default:
9863             break;
9864         }
9865     }
9866
9867     return;
9868 }
9869
9870 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9871 int
9872 CmailLoadGame(f, gameNumber, title, useList)
9873      FILE *f;
9874      int gameNumber;
9875      char *title;
9876      int useList;
9877 {
9878     int retVal;
9879
9880     if (gameNumber > nCmailGames) {
9881         DisplayError(_("No more games in this message"), 0);
9882         return FALSE;
9883     }
9884     if (f == lastLoadGameFP) {
9885         int offset = gameNumber - lastLoadGameNumber;
9886         if (offset == 0) {
9887             cmailMsg[0] = NULLCHAR;
9888             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9889                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9890                 nCmailMovesRegistered--;
9891             }
9892             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9893             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9894                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9895             }
9896         } else {
9897             if (! RegisterMove()) return FALSE;
9898         }
9899     }
9900
9901     retVal = LoadGame(f, gameNumber, title, useList);
9902
9903     /* Make move registered during previous look at this game, if any */
9904     MakeRegisteredMove();
9905
9906     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9907         commentList[currentMove]
9908           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9909         DisplayComment(currentMove - 1, commentList[currentMove]);
9910     }
9911
9912     return retVal;
9913 }
9914
9915 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9916 int
9917 ReloadGame(offset)
9918      int offset;
9919 {
9920     int gameNumber = lastLoadGameNumber + offset;
9921     if (lastLoadGameFP == NULL) {
9922         DisplayError(_("No game has been loaded yet"), 0);
9923         return FALSE;
9924     }
9925     if (gameNumber <= 0) {
9926         DisplayError(_("Can't back up any further"), 0);
9927         return FALSE;
9928     }
9929     if (cmailMsgLoaded) {
9930         return CmailLoadGame(lastLoadGameFP, gameNumber,
9931                              lastLoadGameTitle, lastLoadGameUseList);
9932     } else {
9933         return LoadGame(lastLoadGameFP, gameNumber,
9934                         lastLoadGameTitle, lastLoadGameUseList);
9935     }
9936 }
9937
9938
9939
9940 /* Load the nth game from open file f */
9941 int
9942 LoadGame(f, gameNumber, title, useList)
9943      FILE *f;
9944      int gameNumber;
9945      char *title;
9946      int useList;
9947 {
9948     ChessMove cm;
9949     char buf[MSG_SIZ];
9950     int gn = gameNumber;
9951     ListGame *lg = NULL;
9952     int numPGNTags = 0;
9953     int err;
9954     GameMode oldGameMode;
9955     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9956
9957     if (appData.debugMode) 
9958         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9959
9960     if (gameMode == Training )
9961         SetTrainingModeOff();
9962
9963     oldGameMode = gameMode;
9964     if (gameMode != BeginningOfGame) {
9965       Reset(FALSE, TRUE);
9966     }
9967
9968     gameFileFP = f;
9969     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9970         fclose(lastLoadGameFP);
9971     }
9972
9973     if (useList) {
9974         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9975         
9976         if (lg) {
9977             fseek(f, lg->offset, 0);
9978             GameListHighlight(gameNumber);
9979             gn = 1;
9980         }
9981         else {
9982             DisplayError(_("Game number out of range"), 0);
9983             return FALSE;
9984         }
9985     } else {
9986         GameListDestroy();
9987         if (fseek(f, 0, 0) == -1) {
9988             if (f == lastLoadGameFP ?
9989                 gameNumber == lastLoadGameNumber + 1 :
9990                 gameNumber == 1) {
9991                 gn = 1;
9992             } else {
9993                 DisplayError(_("Can't seek on game file"), 0);
9994                 return FALSE;
9995             }
9996         }
9997     }
9998     lastLoadGameFP = f;
9999     lastLoadGameNumber = gameNumber;
10000     strcpy(lastLoadGameTitle, title);
10001     lastLoadGameUseList = useList;
10002
10003     yynewfile(f);
10004
10005     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10006       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10007                 lg->gameInfo.black);
10008             DisplayTitle(buf);
10009     } else if (*title != NULLCHAR) {
10010         if (gameNumber > 1) {
10011             sprintf(buf, "%s %d", title, gameNumber);
10012             DisplayTitle(buf);
10013         } else {
10014             DisplayTitle(title);
10015         }
10016     }
10017
10018     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10019         gameMode = PlayFromGameFile;
10020         ModeHighlight();
10021     }
10022
10023     currentMove = forwardMostMove = backwardMostMove = 0;
10024     CopyBoard(boards[0], initialPosition);
10025     StopClocks();
10026
10027     /*
10028      * Skip the first gn-1 games in the file.
10029      * Also skip over anything that precedes an identifiable 
10030      * start of game marker, to avoid being confused by 
10031      * garbage at the start of the file.  Currently 
10032      * recognized start of game markers are the move number "1",
10033      * the pattern "gnuchess .* game", the pattern
10034      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10035      * A game that starts with one of the latter two patterns
10036      * will also have a move number 1, possibly
10037      * following a position diagram.
10038      * 5-4-02: Let's try being more lenient and allowing a game to
10039      * start with an unnumbered move.  Does that break anything?
10040      */
10041     cm = lastLoadGameStart = (ChessMove) 0;
10042     while (gn > 0) {
10043         yyboardindex = forwardMostMove;
10044         cm = (ChessMove) yylex();
10045         switch (cm) {
10046           case (ChessMove) 0:
10047             if (cmailMsgLoaded) {
10048                 nCmailGames = CMAIL_MAX_GAMES - gn;
10049             } else {
10050                 Reset(TRUE, TRUE);
10051                 DisplayError(_("Game not found in file"), 0);
10052             }
10053             return FALSE;
10054
10055           case GNUChessGame:
10056           case XBoardGame:
10057             gn--;
10058             lastLoadGameStart = cm;
10059             break;
10060             
10061           case MoveNumberOne:
10062             switch (lastLoadGameStart) {
10063               case GNUChessGame:
10064               case XBoardGame:
10065               case PGNTag:
10066                 break;
10067               case MoveNumberOne:
10068               case (ChessMove) 0:
10069                 gn--;           /* count this game */
10070                 lastLoadGameStart = cm;
10071                 break;
10072               default:
10073                 /* impossible */
10074                 break;
10075             }
10076             break;
10077
10078           case PGNTag:
10079             switch (lastLoadGameStart) {
10080               case GNUChessGame:
10081               case PGNTag:
10082               case MoveNumberOne:
10083               case (ChessMove) 0:
10084                 gn--;           /* count this game */
10085                 lastLoadGameStart = cm;
10086                 break;
10087               case XBoardGame:
10088                 lastLoadGameStart = cm; /* game counted already */
10089                 break;
10090               default:
10091                 /* impossible */
10092                 break;
10093             }
10094             if (gn > 0) {
10095                 do {
10096                     yyboardindex = forwardMostMove;
10097                     cm = (ChessMove) yylex();
10098                 } while (cm == PGNTag || cm == Comment);
10099             }
10100             break;
10101
10102           case WhiteWins:
10103           case BlackWins:
10104           case GameIsDrawn:
10105             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10106                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10107                     != CMAIL_OLD_RESULT) {
10108                     nCmailResults ++ ;
10109                     cmailResult[  CMAIL_MAX_GAMES
10110                                 - gn - 1] = CMAIL_OLD_RESULT;
10111                 }
10112             }
10113             break;
10114
10115           case NormalMove:
10116             /* Only a NormalMove can be at the start of a game
10117              * without a position diagram. */
10118             if (lastLoadGameStart == (ChessMove) 0) {
10119               gn--;
10120               lastLoadGameStart = MoveNumberOne;
10121             }
10122             break;
10123
10124           default:
10125             break;
10126         }
10127     }
10128     
10129     if (appData.debugMode)
10130       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10131
10132     if (cm == XBoardGame) {
10133         /* Skip any header junk before position diagram and/or move 1 */
10134         for (;;) {
10135             yyboardindex = forwardMostMove;
10136             cm = (ChessMove) yylex();
10137
10138             if (cm == (ChessMove) 0 ||
10139                 cm == GNUChessGame || cm == XBoardGame) {
10140                 /* Empty game; pretend end-of-file and handle later */
10141                 cm = (ChessMove) 0;
10142                 break;
10143             }
10144
10145             if (cm == MoveNumberOne || cm == PositionDiagram ||
10146                 cm == PGNTag || cm == Comment)
10147               break;
10148         }
10149     } else if (cm == GNUChessGame) {
10150         if (gameInfo.event != NULL) {
10151             free(gameInfo.event);
10152         }
10153         gameInfo.event = StrSave(yy_text);
10154     }   
10155
10156     startedFromSetupPosition = FALSE;
10157     while (cm == PGNTag) {
10158         if (appData.debugMode) 
10159           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10160         err = ParsePGNTag(yy_text, &gameInfo);
10161         if (!err) numPGNTags++;
10162
10163         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10164         if(gameInfo.variant != oldVariant) {
10165             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10166             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10167             InitPosition(TRUE);
10168             oldVariant = gameInfo.variant;
10169             if (appData.debugMode) 
10170               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10171         }
10172
10173
10174         if (gameInfo.fen != NULL) {
10175           Board initial_position;
10176           startedFromSetupPosition = TRUE;
10177           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10178             Reset(TRUE, TRUE);
10179             DisplayError(_("Bad FEN position in file"), 0);
10180             return FALSE;
10181           }
10182           CopyBoard(boards[0], initial_position);
10183           if (blackPlaysFirst) {
10184             currentMove = forwardMostMove = backwardMostMove = 1;
10185             CopyBoard(boards[1], initial_position);
10186             strcpy(moveList[0], "");
10187             strcpy(parseList[0], "");
10188             timeRemaining[0][1] = whiteTimeRemaining;
10189             timeRemaining[1][1] = blackTimeRemaining;
10190             if (commentList[0] != NULL) {
10191               commentList[1] = commentList[0];
10192               commentList[0] = NULL;
10193             }
10194           } else {
10195             currentMove = forwardMostMove = backwardMostMove = 0;
10196           }
10197           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10198           {   int i;
10199               initialRulePlies = FENrulePlies;
10200               for( i=0; i< nrCastlingRights; i++ )
10201                   initialRights[i] = initial_position[CASTLING][i];
10202           }
10203           yyboardindex = forwardMostMove;
10204           free(gameInfo.fen);
10205           gameInfo.fen = NULL;
10206         }
10207
10208         yyboardindex = forwardMostMove;
10209         cm = (ChessMove) yylex();
10210
10211         /* Handle comments interspersed among the tags */
10212         while (cm == Comment) {
10213             char *p;
10214             if (appData.debugMode) 
10215               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10216             p = yy_text;
10217             AppendComment(currentMove, p, FALSE);
10218             yyboardindex = forwardMostMove;
10219             cm = (ChessMove) yylex();
10220         }
10221     }
10222
10223     /* don't rely on existence of Event tag since if game was
10224      * pasted from clipboard the Event tag may not exist
10225      */
10226     if (numPGNTags > 0){
10227         char *tags;
10228         if (gameInfo.variant == VariantNormal) {
10229           VariantClass v = StringToVariant(gameInfo.event);
10230           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10231           if(v < VariantShogi) gameInfo.variant = v;
10232         }
10233         if (!matchMode) {
10234           if( appData.autoDisplayTags ) {
10235             tags = PGNTags(&gameInfo);
10236             TagsPopUp(tags, CmailMsg());
10237             free(tags);
10238           }
10239         }
10240     } else {
10241         /* Make something up, but don't display it now */
10242         SetGameInfo();
10243         TagsPopDown();
10244     }
10245
10246     if (cm == PositionDiagram) {
10247         int i, j;
10248         char *p;
10249         Board initial_position;
10250
10251         if (appData.debugMode)
10252           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10253
10254         if (!startedFromSetupPosition) {
10255             p = yy_text;
10256             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10257               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10258                 switch (*p) {
10259                   case '[':
10260                   case '-':
10261                   case ' ':
10262                   case '\t':
10263                   case '\n':
10264                   case '\r':
10265                     break;
10266                   default:
10267                     initial_position[i][j++] = CharToPiece(*p);
10268                     break;
10269                 }
10270             while (*p == ' ' || *p == '\t' ||
10271                    *p == '\n' || *p == '\r') p++;
10272         
10273             if (strncmp(p, "black", strlen("black"))==0)
10274               blackPlaysFirst = TRUE;
10275             else
10276               blackPlaysFirst = FALSE;
10277             startedFromSetupPosition = TRUE;
10278         
10279             CopyBoard(boards[0], initial_position);
10280             if (blackPlaysFirst) {
10281                 currentMove = forwardMostMove = backwardMostMove = 1;
10282                 CopyBoard(boards[1], initial_position);
10283                 strcpy(moveList[0], "");
10284                 strcpy(parseList[0], "");
10285                 timeRemaining[0][1] = whiteTimeRemaining;
10286                 timeRemaining[1][1] = blackTimeRemaining;
10287                 if (commentList[0] != NULL) {
10288                     commentList[1] = commentList[0];
10289                     commentList[0] = NULL;
10290                 }
10291             } else {
10292                 currentMove = forwardMostMove = backwardMostMove = 0;
10293             }
10294         }
10295         yyboardindex = forwardMostMove;
10296         cm = (ChessMove) yylex();
10297     }
10298
10299     if (first.pr == NoProc) {
10300         StartChessProgram(&first);
10301     }
10302     InitChessProgram(&first, FALSE);
10303     SendToProgram("force\n", &first);
10304     if (startedFromSetupPosition) {
10305         SendBoard(&first, forwardMostMove);
10306     if (appData.debugMode) {
10307         fprintf(debugFP, "Load Game\n");
10308     }
10309         DisplayBothClocks();
10310     }      
10311
10312     /* [HGM] server: flag to write setup moves in broadcast file as one */
10313     loadFlag = appData.suppressLoadMoves;
10314
10315     while (cm == Comment) {
10316         char *p;
10317         if (appData.debugMode) 
10318           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10319         p = yy_text;
10320         AppendComment(currentMove, p, FALSE);
10321         yyboardindex = forwardMostMove;
10322         cm = (ChessMove) yylex();
10323     }
10324
10325     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10326         cm == WhiteWins || cm == BlackWins ||
10327         cm == GameIsDrawn || cm == GameUnfinished) {
10328         DisplayMessage("", _("No moves in game"));
10329         if (cmailMsgLoaded) {
10330             if (appData.debugMode)
10331               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10332             ClearHighlights();
10333             flipView = FALSE;
10334         }
10335         DrawPosition(FALSE, boards[currentMove]);
10336         DisplayBothClocks();
10337         gameMode = EditGame;
10338         ModeHighlight();
10339         gameFileFP = NULL;
10340         cmailOldMove = 0;
10341         return TRUE;
10342     }
10343
10344     // [HGM] PV info: routine tests if comment empty
10345     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10346         DisplayComment(currentMove - 1, commentList[currentMove]);
10347     }
10348     if (!matchMode && appData.timeDelay != 0) 
10349       DrawPosition(FALSE, boards[currentMove]);
10350
10351     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10352       programStats.ok_to_send = 1;
10353     }
10354
10355     /* if the first token after the PGN tags is a move
10356      * and not move number 1, retrieve it from the parser 
10357      */
10358     if (cm != MoveNumberOne)
10359         LoadGameOneMove(cm);
10360
10361     /* load the remaining moves from the file */
10362     while (LoadGameOneMove((ChessMove)0)) {
10363       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10364       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10365     }
10366
10367     /* rewind to the start of the game */
10368     currentMove = backwardMostMove;
10369
10370     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10371
10372     if (oldGameMode == AnalyzeFile ||
10373         oldGameMode == AnalyzeMode) {
10374       AnalyzeFileEvent();
10375     }
10376
10377     if (matchMode || appData.timeDelay == 0) {
10378       ToEndEvent();
10379       gameMode = EditGame;
10380       ModeHighlight();
10381     } else if (appData.timeDelay > 0) {
10382       AutoPlayGameLoop();
10383     }
10384
10385     if (appData.debugMode) 
10386         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10387
10388     loadFlag = 0; /* [HGM] true game starts */
10389     return TRUE;
10390 }
10391
10392 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10393 int
10394 ReloadPosition(offset)
10395      int offset;
10396 {
10397     int positionNumber = lastLoadPositionNumber + offset;
10398     if (lastLoadPositionFP == NULL) {
10399         DisplayError(_("No position has been loaded yet"), 0);
10400         return FALSE;
10401     }
10402     if (positionNumber <= 0) {
10403         DisplayError(_("Can't back up any further"), 0);
10404         return FALSE;
10405     }
10406     return LoadPosition(lastLoadPositionFP, positionNumber,
10407                         lastLoadPositionTitle);
10408 }
10409
10410 /* Load the nth position from the given file */
10411 int
10412 LoadPositionFromFile(filename, n, title)
10413      char *filename;
10414      int n;
10415      char *title;
10416 {
10417     FILE *f;
10418     char buf[MSG_SIZ];
10419
10420     if (strcmp(filename, "-") == 0) {
10421         return LoadPosition(stdin, n, "stdin");
10422     } else {
10423         f = fopen(filename, "rb");
10424         if (f == NULL) {
10425             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10426             DisplayError(buf, errno);
10427             return FALSE;
10428         } else {
10429             return LoadPosition(f, n, title);
10430         }
10431     }
10432 }
10433
10434 /* Load the nth position from the given open file, and close it */
10435 int
10436 LoadPosition(f, positionNumber, title)
10437      FILE *f;
10438      int positionNumber;
10439      char *title;
10440 {
10441     char *p, line[MSG_SIZ];
10442     Board initial_position;
10443     int i, j, fenMode, pn;
10444     
10445     if (gameMode == Training )
10446         SetTrainingModeOff();
10447
10448     if (gameMode != BeginningOfGame) {
10449         Reset(FALSE, TRUE);
10450     }
10451     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10452         fclose(lastLoadPositionFP);
10453     }
10454     if (positionNumber == 0) positionNumber = 1;
10455     lastLoadPositionFP = f;
10456     lastLoadPositionNumber = positionNumber;
10457     strcpy(lastLoadPositionTitle, title);
10458     if (first.pr == NoProc) {
10459       StartChessProgram(&first);
10460       InitChessProgram(&first, FALSE);
10461     }    
10462     pn = positionNumber;
10463     if (positionNumber < 0) {
10464         /* Negative position number means to seek to that byte offset */
10465         if (fseek(f, -positionNumber, 0) == -1) {
10466             DisplayError(_("Can't seek on position file"), 0);
10467             return FALSE;
10468         };
10469         pn = 1;
10470     } else {
10471         if (fseek(f, 0, 0) == -1) {
10472             if (f == lastLoadPositionFP ?
10473                 positionNumber == lastLoadPositionNumber + 1 :
10474                 positionNumber == 1) {
10475                 pn = 1;
10476             } else {
10477                 DisplayError(_("Can't seek on position file"), 0);
10478                 return FALSE;
10479             }
10480         }
10481     }
10482     /* See if this file is FEN or old-style xboard */
10483     if (fgets(line, MSG_SIZ, f) == NULL) {
10484         DisplayError(_("Position not found in file"), 0);
10485         return FALSE;
10486     }
10487     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10488     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10489
10490     if (pn >= 2) {
10491         if (fenMode || line[0] == '#') pn--;
10492         while (pn > 0) {
10493             /* skip positions before number pn */
10494             if (fgets(line, MSG_SIZ, f) == NULL) {
10495                 Reset(TRUE, TRUE);
10496                 DisplayError(_("Position not found in file"), 0);
10497                 return FALSE;
10498             }
10499             if (fenMode || line[0] == '#') pn--;
10500         }
10501     }
10502
10503     if (fenMode) {
10504         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10505             DisplayError(_("Bad FEN position in file"), 0);
10506             return FALSE;
10507         }
10508     } else {
10509         (void) fgets(line, MSG_SIZ, f);
10510         (void) fgets(line, MSG_SIZ, f);
10511     
10512         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10513             (void) fgets(line, MSG_SIZ, f);
10514             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10515                 if (*p == ' ')
10516                   continue;
10517                 initial_position[i][j++] = CharToPiece(*p);
10518             }
10519         }
10520     
10521         blackPlaysFirst = FALSE;
10522         if (!feof(f)) {
10523             (void) fgets(line, MSG_SIZ, f);
10524             if (strncmp(line, "black", strlen("black"))==0)
10525               blackPlaysFirst = TRUE;
10526         }
10527     }
10528     startedFromSetupPosition = TRUE;
10529     
10530     SendToProgram("force\n", &first);
10531     CopyBoard(boards[0], initial_position);
10532     if (blackPlaysFirst) {
10533         currentMove = forwardMostMove = backwardMostMove = 1;
10534         strcpy(moveList[0], "");
10535         strcpy(parseList[0], "");
10536         CopyBoard(boards[1], initial_position);
10537         DisplayMessage("", _("Black to play"));
10538     } else {
10539         currentMove = forwardMostMove = backwardMostMove = 0;
10540         DisplayMessage("", _("White to play"));
10541     }
10542     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10543     SendBoard(&first, forwardMostMove);
10544     if (appData.debugMode) {
10545 int i, j;
10546   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10547   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10548         fprintf(debugFP, "Load Position\n");
10549     }
10550
10551     if (positionNumber > 1) {
10552         sprintf(line, "%s %d", title, positionNumber);
10553         DisplayTitle(line);
10554     } else {
10555         DisplayTitle(title);
10556     }
10557     gameMode = EditGame;
10558     ModeHighlight();
10559     ResetClocks();
10560     timeRemaining[0][1] = whiteTimeRemaining;
10561     timeRemaining[1][1] = blackTimeRemaining;
10562     DrawPosition(FALSE, boards[currentMove]);
10563    
10564     return TRUE;
10565 }
10566
10567
10568 void
10569 CopyPlayerNameIntoFileName(dest, src)
10570      char **dest, *src;
10571 {
10572     while (*src != NULLCHAR && *src != ',') {
10573         if (*src == ' ') {
10574             *(*dest)++ = '_';
10575             src++;
10576         } else {
10577             *(*dest)++ = *src++;
10578         }
10579     }
10580 }
10581
10582 char *DefaultFileName(ext)
10583      char *ext;
10584 {
10585     static char def[MSG_SIZ];
10586     char *p;
10587
10588     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10589         p = def;
10590         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10591         *p++ = '-';
10592         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10593         *p++ = '.';
10594         strcpy(p, ext);
10595     } else {
10596         def[0] = NULLCHAR;
10597     }
10598     return def;
10599 }
10600
10601 /* Save the current game to the given file */
10602 int
10603 SaveGameToFile(filename, append)
10604      char *filename;
10605      int append;
10606 {
10607     FILE *f;
10608     char buf[MSG_SIZ];
10609
10610     if (strcmp(filename, "-") == 0) {
10611         return SaveGame(stdout, 0, NULL);
10612     } else {
10613         f = fopen(filename, append ? "a" : "w");
10614         if (f == NULL) {
10615             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10616             DisplayError(buf, errno);
10617             return FALSE;
10618         } else {
10619             return SaveGame(f, 0, NULL);
10620         }
10621     }
10622 }
10623
10624 char *
10625 SavePart(str)
10626      char *str;
10627 {
10628     static char buf[MSG_SIZ];
10629     char *p;
10630     
10631     p = strchr(str, ' ');
10632     if (p == NULL) return str;
10633     strncpy(buf, str, p - str);
10634     buf[p - str] = NULLCHAR;
10635     return buf;
10636 }
10637
10638 #define PGN_MAX_LINE 75
10639
10640 #define PGN_SIDE_WHITE  0
10641 #define PGN_SIDE_BLACK  1
10642
10643 /* [AS] */
10644 static int FindFirstMoveOutOfBook( int side )
10645 {
10646     int result = -1;
10647
10648     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10649         int index = backwardMostMove;
10650         int has_book_hit = 0;
10651
10652         if( (index % 2) != side ) {
10653             index++;
10654         }
10655
10656         while( index < forwardMostMove ) {
10657             /* Check to see if engine is in book */
10658             int depth = pvInfoList[index].depth;
10659             int score = pvInfoList[index].score;
10660             int in_book = 0;
10661
10662             if( depth <= 2 ) {
10663                 in_book = 1;
10664             }
10665             else if( score == 0 && depth == 63 ) {
10666                 in_book = 1; /* Zappa */
10667             }
10668             else if( score == 2 && depth == 99 ) {
10669                 in_book = 1; /* Abrok */
10670             }
10671
10672             has_book_hit += in_book;
10673
10674             if( ! in_book ) {
10675                 result = index;
10676
10677                 break;
10678             }
10679
10680             index += 2;
10681         }
10682     }
10683
10684     return result;
10685 }
10686
10687 /* [AS] */
10688 void GetOutOfBookInfo( char * buf )
10689 {
10690     int oob[2];
10691     int i;
10692     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10693
10694     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10695     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10696
10697     *buf = '\0';
10698
10699     if( oob[0] >= 0 || oob[1] >= 0 ) {
10700         for( i=0; i<2; i++ ) {
10701             int idx = oob[i];
10702
10703             if( idx >= 0 ) {
10704                 if( i > 0 && oob[0] >= 0 ) {
10705                     strcat( buf, "   " );
10706                 }
10707
10708                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10709                 sprintf( buf+strlen(buf), "%s%.2f", 
10710                     pvInfoList[idx].score >= 0 ? "+" : "",
10711                     pvInfoList[idx].score / 100.0 );
10712             }
10713         }
10714     }
10715 }
10716
10717 /* Save game in PGN style and close the file */
10718 int
10719 SaveGamePGN(f)
10720      FILE *f;
10721 {
10722     int i, offset, linelen, newblock;
10723     time_t tm;
10724 //    char *movetext;
10725     char numtext[32];
10726     int movelen, numlen, blank;
10727     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10728
10729     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10730     
10731     tm = time((time_t *) NULL);
10732     
10733     PrintPGNTags(f, &gameInfo);
10734     
10735     if (backwardMostMove > 0 || startedFromSetupPosition) {
10736         char *fen = PositionToFEN(backwardMostMove, NULL);
10737         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10738         fprintf(f, "\n{--------------\n");
10739         PrintPosition(f, backwardMostMove);
10740         fprintf(f, "--------------}\n");
10741         free(fen);
10742     }
10743     else {
10744         /* [AS] Out of book annotation */
10745         if( appData.saveOutOfBookInfo ) {
10746             char buf[64];
10747
10748             GetOutOfBookInfo( buf );
10749
10750             if( buf[0] != '\0' ) {
10751                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10752             }
10753         }
10754
10755         fprintf(f, "\n");
10756     }
10757
10758     i = backwardMostMove;
10759     linelen = 0;
10760     newblock = TRUE;
10761
10762     while (i < forwardMostMove) {
10763         /* Print comments preceding this move */
10764         if (commentList[i] != NULL) {
10765             if (linelen > 0) fprintf(f, "\n");
10766             fprintf(f, "%s", commentList[i]);
10767             linelen = 0;
10768             newblock = TRUE;
10769         }
10770
10771         /* Format move number */
10772         if ((i % 2) == 0) {
10773             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10774         } else {
10775             if (newblock) {
10776                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10777             } else {
10778                 numtext[0] = NULLCHAR;
10779             }
10780         }
10781         numlen = strlen(numtext);
10782         newblock = FALSE;
10783
10784         /* Print move number */
10785         blank = linelen > 0 && numlen > 0;
10786         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10787             fprintf(f, "\n");
10788             linelen = 0;
10789             blank = 0;
10790         }
10791         if (blank) {
10792             fprintf(f, " ");
10793             linelen++;
10794         }
10795         fprintf(f, "%s", numtext);
10796         linelen += numlen;
10797
10798         /* Get move */
10799         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10800         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10801
10802         /* Print move */
10803         blank = linelen > 0 && movelen > 0;
10804         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10805             fprintf(f, "\n");
10806             linelen = 0;
10807             blank = 0;
10808         }
10809         if (blank) {
10810             fprintf(f, " ");
10811             linelen++;
10812         }
10813         fprintf(f, "%s", move_buffer);
10814         linelen += movelen;
10815
10816         /* [AS] Add PV info if present */
10817         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10818             /* [HGM] add time */
10819             char buf[MSG_SIZ]; int seconds;
10820
10821             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10822
10823             if( seconds <= 0) buf[0] = 0; else
10824             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10825                 seconds = (seconds + 4)/10; // round to full seconds
10826                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10827                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10828             }
10829
10830             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10831                 pvInfoList[i].score >= 0 ? "+" : "",
10832                 pvInfoList[i].score / 100.0,
10833                 pvInfoList[i].depth,
10834                 buf );
10835
10836             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10837
10838             /* Print score/depth */
10839             blank = linelen > 0 && movelen > 0;
10840             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10841                 fprintf(f, "\n");
10842                 linelen = 0;
10843                 blank = 0;
10844             }
10845             if (blank) {
10846                 fprintf(f, " ");
10847                 linelen++;
10848             }
10849             fprintf(f, "%s", move_buffer);
10850             linelen += movelen;
10851         }
10852
10853         i++;
10854     }
10855     
10856     /* Start a new line */
10857     if (linelen > 0) fprintf(f, "\n");
10858
10859     /* Print comments after last move */
10860     if (commentList[i] != NULL) {
10861         fprintf(f, "%s\n", commentList[i]);
10862     }
10863
10864     /* Print result */
10865     if (gameInfo.resultDetails != NULL &&
10866         gameInfo.resultDetails[0] != NULLCHAR) {
10867         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10868                 PGNResult(gameInfo.result));
10869     } else {
10870         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10871     }
10872
10873     fclose(f);
10874     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10875     return TRUE;
10876 }
10877
10878 /* Save game in old style and close the file */
10879 int
10880 SaveGameOldStyle(f)
10881      FILE *f;
10882 {
10883     int i, offset;
10884     time_t tm;
10885     
10886     tm = time((time_t *) NULL);
10887     
10888     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10889     PrintOpponents(f);
10890     
10891     if (backwardMostMove > 0 || startedFromSetupPosition) {
10892         fprintf(f, "\n[--------------\n");
10893         PrintPosition(f, backwardMostMove);
10894         fprintf(f, "--------------]\n");
10895     } else {
10896         fprintf(f, "\n");
10897     }
10898
10899     i = backwardMostMove;
10900     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10901
10902     while (i < forwardMostMove) {
10903         if (commentList[i] != NULL) {
10904             fprintf(f, "[%s]\n", commentList[i]);
10905         }
10906
10907         if ((i % 2) == 1) {
10908             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10909             i++;
10910         } else {
10911             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10912             i++;
10913             if (commentList[i] != NULL) {
10914                 fprintf(f, "\n");
10915                 continue;
10916             }
10917             if (i >= forwardMostMove) {
10918                 fprintf(f, "\n");
10919                 break;
10920             }
10921             fprintf(f, "%s\n", parseList[i]);
10922             i++;
10923         }
10924     }
10925     
10926     if (commentList[i] != NULL) {
10927         fprintf(f, "[%s]\n", commentList[i]);
10928     }
10929
10930     /* This isn't really the old style, but it's close enough */
10931     if (gameInfo.resultDetails != NULL &&
10932         gameInfo.resultDetails[0] != NULLCHAR) {
10933         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10934                 gameInfo.resultDetails);
10935     } else {
10936         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10937     }
10938
10939     fclose(f);
10940     return TRUE;
10941 }
10942
10943 /* Save the current game to open file f and close the file */
10944 int
10945 SaveGame(f, dummy, dummy2)
10946      FILE *f;
10947      int dummy;
10948      char *dummy2;
10949 {
10950     if (gameMode == EditPosition) EditPositionDone(TRUE);
10951     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10952     if (appData.oldSaveStyle)
10953       return SaveGameOldStyle(f);
10954     else
10955       return SaveGamePGN(f);
10956 }
10957
10958 /* Save the current position to the given file */
10959 int
10960 SavePositionToFile(filename)
10961      char *filename;
10962 {
10963     FILE *f;
10964     char buf[MSG_SIZ];
10965
10966     if (strcmp(filename, "-") == 0) {
10967         return SavePosition(stdout, 0, NULL);
10968     } else {
10969         f = fopen(filename, "a");
10970         if (f == NULL) {
10971             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10972             DisplayError(buf, errno);
10973             return FALSE;
10974         } else {
10975             SavePosition(f, 0, NULL);
10976             return TRUE;
10977         }
10978     }
10979 }
10980
10981 /* Save the current position to the given open file and close the file */
10982 int
10983 SavePosition(f, dummy, dummy2)
10984      FILE *f;
10985      int dummy;
10986      char *dummy2;
10987 {
10988     time_t tm;
10989     char *fen;
10990     
10991     if (gameMode == EditPosition) EditPositionDone(TRUE);
10992     if (appData.oldSaveStyle) {
10993         tm = time((time_t *) NULL);
10994     
10995         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10996         PrintOpponents(f);
10997         fprintf(f, "[--------------\n");
10998         PrintPosition(f, currentMove);
10999         fprintf(f, "--------------]\n");
11000     } else {
11001         fen = PositionToFEN(currentMove, NULL);
11002         fprintf(f, "%s\n", fen);
11003         free(fen);
11004     }
11005     fclose(f);
11006     return TRUE;
11007 }
11008
11009 void
11010 ReloadCmailMsgEvent(unregister)
11011      int unregister;
11012 {
11013 #if !WIN32
11014     static char *inFilename = NULL;
11015     static char *outFilename;
11016     int i;
11017     struct stat inbuf, outbuf;
11018     int status;
11019     
11020     /* Any registered moves are unregistered if unregister is set, */
11021     /* i.e. invoked by the signal handler */
11022     if (unregister) {
11023         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11024             cmailMoveRegistered[i] = FALSE;
11025             if (cmailCommentList[i] != NULL) {
11026                 free(cmailCommentList[i]);
11027                 cmailCommentList[i] = NULL;
11028             }
11029         }
11030         nCmailMovesRegistered = 0;
11031     }
11032
11033     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11034         cmailResult[i] = CMAIL_NOT_RESULT;
11035     }
11036     nCmailResults = 0;
11037
11038     if (inFilename == NULL) {
11039         /* Because the filenames are static they only get malloced once  */
11040         /* and they never get freed                                      */
11041         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11042         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11043
11044         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11045         sprintf(outFilename, "%s.out", appData.cmailGameName);
11046     }
11047     
11048     status = stat(outFilename, &outbuf);
11049     if (status < 0) {
11050         cmailMailedMove = FALSE;
11051     } else {
11052         status = stat(inFilename, &inbuf);
11053         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11054     }
11055     
11056     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11057        counts the games, notes how each one terminated, etc.
11058        
11059        It would be nice to remove this kludge and instead gather all
11060        the information while building the game list.  (And to keep it
11061        in the game list nodes instead of having a bunch of fixed-size
11062        parallel arrays.)  Note this will require getting each game's
11063        termination from the PGN tags, as the game list builder does
11064        not process the game moves.  --mann
11065        */
11066     cmailMsgLoaded = TRUE;
11067     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11068     
11069     /* Load first game in the file or popup game menu */
11070     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11071
11072 #endif /* !WIN32 */
11073     return;
11074 }
11075
11076 int
11077 RegisterMove()
11078 {
11079     FILE *f;
11080     char string[MSG_SIZ];
11081
11082     if (   cmailMailedMove
11083         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11084         return TRUE;            /* Allow free viewing  */
11085     }
11086
11087     /* Unregister move to ensure that we don't leave RegisterMove        */
11088     /* with the move registered when the conditions for registering no   */
11089     /* longer hold                                                       */
11090     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11091         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11092         nCmailMovesRegistered --;
11093
11094         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11095           {
11096               free(cmailCommentList[lastLoadGameNumber - 1]);
11097               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11098           }
11099     }
11100
11101     if (cmailOldMove == -1) {
11102         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11103         return FALSE;
11104     }
11105
11106     if (currentMove > cmailOldMove + 1) {
11107         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11108         return FALSE;
11109     }
11110
11111     if (currentMove < cmailOldMove) {
11112         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11113         return FALSE;
11114     }
11115
11116     if (forwardMostMove > currentMove) {
11117         /* Silently truncate extra moves */
11118         TruncateGame();
11119     }
11120
11121     if (   (currentMove == cmailOldMove + 1)
11122         || (   (currentMove == cmailOldMove)
11123             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11124                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11125         if (gameInfo.result != GameUnfinished) {
11126             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11127         }
11128
11129         if (commentList[currentMove] != NULL) {
11130             cmailCommentList[lastLoadGameNumber - 1]
11131               = StrSave(commentList[currentMove]);
11132         }
11133         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11134
11135         if (appData.debugMode)
11136           fprintf(debugFP, "Saving %s for game %d\n",
11137                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11138
11139         sprintf(string,
11140                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11141         
11142         f = fopen(string, "w");
11143         if (appData.oldSaveStyle) {
11144             SaveGameOldStyle(f); /* also closes the file */
11145             
11146             sprintf(string, "%s.pos.out", appData.cmailGameName);
11147             f = fopen(string, "w");
11148             SavePosition(f, 0, NULL); /* also closes the file */
11149         } else {
11150             fprintf(f, "{--------------\n");
11151             PrintPosition(f, currentMove);
11152             fprintf(f, "--------------}\n\n");
11153             
11154             SaveGame(f, 0, NULL); /* also closes the file*/
11155         }
11156         
11157         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11158         nCmailMovesRegistered ++;
11159     } else if (nCmailGames == 1) {
11160         DisplayError(_("You have not made a move yet"), 0);
11161         return FALSE;
11162     }
11163
11164     return TRUE;
11165 }
11166
11167 void
11168 MailMoveEvent()
11169 {
11170 #if !WIN32
11171     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11172     FILE *commandOutput;
11173     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11174     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11175     int nBuffers;
11176     int i;
11177     int archived;
11178     char *arcDir;
11179
11180     if (! cmailMsgLoaded) {
11181         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11182         return;
11183     }
11184
11185     if (nCmailGames == nCmailResults) {
11186         DisplayError(_("No unfinished games"), 0);
11187         return;
11188     }
11189
11190 #if CMAIL_PROHIBIT_REMAIL
11191     if (cmailMailedMove) {
11192         sprintf(msg, _("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);
11193         DisplayError(msg, 0);
11194         return;
11195     }
11196 #endif
11197
11198     if (! (cmailMailedMove || RegisterMove())) return;
11199     
11200     if (   cmailMailedMove
11201         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11202         sprintf(string, partCommandString,
11203                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11204         commandOutput = popen(string, "r");
11205
11206         if (commandOutput == NULL) {
11207             DisplayError(_("Failed to invoke cmail"), 0);
11208         } else {
11209             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11210                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11211             }
11212             if (nBuffers > 1) {
11213                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11214                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11215                 nBytes = MSG_SIZ - 1;
11216             } else {
11217                 (void) memcpy(msg, buffer, nBytes);
11218             }
11219             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11220
11221             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11222                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11223
11224                 archived = TRUE;
11225                 for (i = 0; i < nCmailGames; i ++) {
11226                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11227                         archived = FALSE;
11228                     }
11229                 }
11230                 if (   archived
11231                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11232                         != NULL)) {
11233                     sprintf(buffer, "%s/%s.%s.archive",
11234                             arcDir,
11235                             appData.cmailGameName,
11236                             gameInfo.date);
11237                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11238                     cmailMsgLoaded = FALSE;
11239                 }
11240             }
11241
11242             DisplayInformation(msg);
11243             pclose(commandOutput);
11244         }
11245     } else {
11246         if ((*cmailMsg) != '\0') {
11247             DisplayInformation(cmailMsg);
11248         }
11249     }
11250
11251     return;
11252 #endif /* !WIN32 */
11253 }
11254
11255 char *
11256 CmailMsg()
11257 {
11258 #if WIN32
11259     return NULL;
11260 #else
11261     int  prependComma = 0;
11262     char number[5];
11263     char string[MSG_SIZ];       /* Space for game-list */
11264     int  i;
11265     
11266     if (!cmailMsgLoaded) return "";
11267
11268     if (cmailMailedMove) {
11269         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11270     } else {
11271         /* Create a list of games left */
11272         sprintf(string, "[");
11273         for (i = 0; i < nCmailGames; i ++) {
11274             if (! (   cmailMoveRegistered[i]
11275                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11276                 if (prependComma) {
11277                     sprintf(number, ",%d", i + 1);
11278                 } else {
11279                     sprintf(number, "%d", i + 1);
11280                     prependComma = 1;
11281                 }
11282                 
11283                 strcat(string, number);
11284             }
11285         }
11286         strcat(string, "]");
11287
11288         if (nCmailMovesRegistered + nCmailResults == 0) {
11289             switch (nCmailGames) {
11290               case 1:
11291                 sprintf(cmailMsg,
11292                         _("Still need to make move for game\n"));
11293                 break;
11294                 
11295               case 2:
11296                 sprintf(cmailMsg,
11297                         _("Still need to make moves for both games\n"));
11298                 break;
11299                 
11300               default:
11301                 sprintf(cmailMsg,
11302                         _("Still need to make moves for all %d games\n"),
11303                         nCmailGames);
11304                 break;
11305             }
11306         } else {
11307             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11308               case 1:
11309                 sprintf(cmailMsg,
11310                         _("Still need to make a move for game %s\n"),
11311                         string);
11312                 break;
11313                 
11314               case 0:
11315                 if (nCmailResults == nCmailGames) {
11316                     sprintf(cmailMsg, _("No unfinished games\n"));
11317                 } else {
11318                     sprintf(cmailMsg, _("Ready to send mail\n"));
11319                 }
11320                 break;
11321                 
11322               default:
11323                 sprintf(cmailMsg,
11324                         _("Still need to make moves for games %s\n"),
11325                         string);
11326             }
11327         }
11328     }
11329     return cmailMsg;
11330 #endif /* WIN32 */
11331 }
11332
11333 void
11334 ResetGameEvent()
11335 {
11336     if (gameMode == Training)
11337       SetTrainingModeOff();
11338
11339     Reset(TRUE, TRUE);
11340     cmailMsgLoaded = FALSE;
11341     if (appData.icsActive) {
11342       SendToICS(ics_prefix);
11343       SendToICS("refresh\n");
11344     }
11345 }
11346
11347 void
11348 ExitEvent(status)
11349      int status;
11350 {
11351     exiting++;
11352     if (exiting > 2) {
11353       /* Give up on clean exit */
11354       exit(status);
11355     }
11356     if (exiting > 1) {
11357       /* Keep trying for clean exit */
11358       return;
11359     }
11360
11361     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11362
11363     if (telnetISR != NULL) {
11364       RemoveInputSource(telnetISR);
11365     }
11366     if (icsPR != NoProc) {
11367       DestroyChildProcess(icsPR, TRUE);
11368     }
11369
11370     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11371     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11372
11373     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11374     /* make sure this other one finishes before killing it!                  */
11375     if(endingGame) { int count = 0;
11376         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11377         while(endingGame && count++ < 10) DoSleep(1);
11378         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11379     }
11380
11381     /* Kill off chess programs */
11382     if (first.pr != NoProc) {
11383         ExitAnalyzeMode();
11384         
11385         DoSleep( appData.delayBeforeQuit );
11386         SendToProgram("quit\n", &first);
11387         DoSleep( appData.delayAfterQuit );
11388         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11389     }
11390     if (second.pr != NoProc) {
11391         DoSleep( appData.delayBeforeQuit );
11392         SendToProgram("quit\n", &second);
11393         DoSleep( appData.delayAfterQuit );
11394         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11395     }
11396     if (first.isr != NULL) {
11397         RemoveInputSource(first.isr);
11398     }
11399     if (second.isr != NULL) {
11400         RemoveInputSource(second.isr);
11401     }
11402
11403     ShutDownFrontEnd();
11404     exit(status);
11405 }
11406
11407 void
11408 PauseEvent()
11409 {
11410     if (appData.debugMode)
11411         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11412     if (pausing) {
11413         pausing = FALSE;
11414         ModeHighlight();
11415         if (gameMode == MachinePlaysWhite ||
11416             gameMode == MachinePlaysBlack) {
11417             StartClocks();
11418         } else {
11419             DisplayBothClocks();
11420         }
11421         if (gameMode == PlayFromGameFile) {
11422             if (appData.timeDelay >= 0) 
11423                 AutoPlayGameLoop();
11424         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11425             Reset(FALSE, TRUE);
11426             SendToICS(ics_prefix);
11427             SendToICS("refresh\n");
11428         } else if (currentMove < forwardMostMove) {
11429             ForwardInner(forwardMostMove);
11430         }
11431         pauseExamInvalid = FALSE;
11432     } else {
11433         switch (gameMode) {
11434           default:
11435             return;
11436           case IcsExamining:
11437             pauseExamForwardMostMove = forwardMostMove;
11438             pauseExamInvalid = FALSE;
11439             /* fall through */
11440           case IcsObserving:
11441           case IcsPlayingWhite:
11442           case IcsPlayingBlack:
11443             pausing = TRUE;
11444             ModeHighlight();
11445             return;
11446           case PlayFromGameFile:
11447             (void) StopLoadGameTimer();
11448             pausing = TRUE;
11449             ModeHighlight();
11450             break;
11451           case BeginningOfGame:
11452             if (appData.icsActive) return;
11453             /* else fall through */
11454           case MachinePlaysWhite:
11455           case MachinePlaysBlack:
11456           case TwoMachinesPlay:
11457             if (forwardMostMove == 0)
11458               return;           /* don't pause if no one has moved */
11459             if ((gameMode == MachinePlaysWhite &&
11460                  !WhiteOnMove(forwardMostMove)) ||
11461                 (gameMode == MachinePlaysBlack &&
11462                  WhiteOnMove(forwardMostMove))) {
11463                 StopClocks();
11464             }
11465             pausing = TRUE;
11466             ModeHighlight();
11467             break;
11468         }
11469     }
11470 }
11471
11472 void
11473 EditCommentEvent()
11474 {
11475     char title[MSG_SIZ];
11476
11477     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11478         strcpy(title, _("Edit comment"));
11479     } else {
11480         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11481                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11482                 parseList[currentMove - 1]);
11483     }
11484
11485     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11486 }
11487
11488
11489 void
11490 EditTagsEvent()
11491 {
11492     char *tags = PGNTags(&gameInfo);
11493     EditTagsPopUp(tags);
11494     free(tags);
11495 }
11496
11497 void
11498 AnalyzeModeEvent()
11499 {
11500     if (appData.noChessProgram || gameMode == AnalyzeMode)
11501       return;
11502
11503     if (gameMode != AnalyzeFile) {
11504         if (!appData.icsEngineAnalyze) {
11505                EditGameEvent();
11506                if (gameMode != EditGame) return;
11507         }
11508         ResurrectChessProgram();
11509         SendToProgram("analyze\n", &first);
11510         first.analyzing = TRUE;
11511         /*first.maybeThinking = TRUE;*/
11512         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11513         EngineOutputPopUp();
11514     }
11515     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11516     pausing = FALSE;
11517     ModeHighlight();
11518     SetGameInfo();
11519
11520     StartAnalysisClock();
11521     GetTimeMark(&lastNodeCountTime);
11522     lastNodeCount = 0;
11523 }
11524
11525 void
11526 AnalyzeFileEvent()
11527 {
11528     if (appData.noChessProgram || gameMode == AnalyzeFile)
11529       return;
11530
11531     if (gameMode != AnalyzeMode) {
11532         EditGameEvent();
11533         if (gameMode != EditGame) return;
11534         ResurrectChessProgram();
11535         SendToProgram("analyze\n", &first);
11536         first.analyzing = TRUE;
11537         /*first.maybeThinking = TRUE;*/
11538         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11539         EngineOutputPopUp();
11540     }
11541     gameMode = AnalyzeFile;
11542     pausing = FALSE;
11543     ModeHighlight();
11544     SetGameInfo();
11545
11546     StartAnalysisClock();
11547     GetTimeMark(&lastNodeCountTime);
11548     lastNodeCount = 0;
11549 }
11550
11551 void
11552 MachineWhiteEvent()
11553 {
11554     char buf[MSG_SIZ];
11555     char *bookHit = NULL;
11556
11557     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11558       return;
11559
11560
11561     if (gameMode == PlayFromGameFile || 
11562         gameMode == TwoMachinesPlay  || 
11563         gameMode == Training         || 
11564         gameMode == AnalyzeMode      || 
11565         gameMode == EndOfGame)
11566         EditGameEvent();
11567
11568     if (gameMode == EditPosition) 
11569         EditPositionDone(TRUE);
11570
11571     if (!WhiteOnMove(currentMove)) {
11572         DisplayError(_("It is not White's turn"), 0);
11573         return;
11574     }
11575   
11576     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11577       ExitAnalyzeMode();
11578
11579     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11580         gameMode == AnalyzeFile)
11581         TruncateGame();
11582
11583     ResurrectChessProgram();    /* in case it isn't running */
11584     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11585         gameMode = MachinePlaysWhite;
11586         ResetClocks();
11587     } else
11588     gameMode = MachinePlaysWhite;
11589     pausing = FALSE;
11590     ModeHighlight();
11591     SetGameInfo();
11592     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11593     DisplayTitle(buf);
11594     if (first.sendName) {
11595       sprintf(buf, "name %s\n", gameInfo.black);
11596       SendToProgram(buf, &first);
11597     }
11598     if (first.sendTime) {
11599       if (first.useColors) {
11600         SendToProgram("black\n", &first); /*gnu kludge*/
11601       }
11602       SendTimeRemaining(&first, TRUE);
11603     }
11604     if (first.useColors) {
11605       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11606     }
11607     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11608     SetMachineThinkingEnables();
11609     first.maybeThinking = TRUE;
11610     StartClocks();
11611     firstMove = FALSE;
11612
11613     if (appData.autoFlipView && !flipView) {
11614       flipView = !flipView;
11615       DrawPosition(FALSE, NULL);
11616       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11617     }
11618
11619     if(bookHit) { // [HGM] book: simulate book reply
11620         static char bookMove[MSG_SIZ]; // a bit generous?
11621
11622         programStats.nodes = programStats.depth = programStats.time = 
11623         programStats.score = programStats.got_only_move = 0;
11624         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11625
11626         strcpy(bookMove, "move ");
11627         strcat(bookMove, bookHit);
11628         HandleMachineMove(bookMove, &first);
11629     }
11630 }
11631
11632 void
11633 MachineBlackEvent()
11634 {
11635     char buf[MSG_SIZ];
11636    char *bookHit = NULL;
11637
11638     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11639         return;
11640
11641
11642     if (gameMode == PlayFromGameFile || 
11643         gameMode == TwoMachinesPlay  || 
11644         gameMode == Training         || 
11645         gameMode == AnalyzeMode      || 
11646         gameMode == EndOfGame)
11647         EditGameEvent();
11648
11649     if (gameMode == EditPosition) 
11650         EditPositionDone(TRUE);
11651
11652     if (WhiteOnMove(currentMove)) {
11653         DisplayError(_("It is not Black's turn"), 0);
11654         return;
11655     }
11656     
11657     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11658       ExitAnalyzeMode();
11659
11660     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11661         gameMode == AnalyzeFile)
11662         TruncateGame();
11663
11664     ResurrectChessProgram();    /* in case it isn't running */
11665     gameMode = MachinePlaysBlack;
11666     pausing = FALSE;
11667     ModeHighlight();
11668     SetGameInfo();
11669     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11670     DisplayTitle(buf);
11671     if (first.sendName) {
11672       sprintf(buf, "name %s\n", gameInfo.white);
11673       SendToProgram(buf, &first);
11674     }
11675     if (first.sendTime) {
11676       if (first.useColors) {
11677         SendToProgram("white\n", &first); /*gnu kludge*/
11678       }
11679       SendTimeRemaining(&first, FALSE);
11680     }
11681     if (first.useColors) {
11682       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11683     }
11684     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11685     SetMachineThinkingEnables();
11686     first.maybeThinking = TRUE;
11687     StartClocks();
11688
11689     if (appData.autoFlipView && flipView) {
11690       flipView = !flipView;
11691       DrawPosition(FALSE, NULL);
11692       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11693     }
11694     if(bookHit) { // [HGM] book: simulate book reply
11695         static char bookMove[MSG_SIZ]; // a bit generous?
11696
11697         programStats.nodes = programStats.depth = programStats.time = 
11698         programStats.score = programStats.got_only_move = 0;
11699         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11700
11701         strcpy(bookMove, "move ");
11702         strcat(bookMove, bookHit);
11703         HandleMachineMove(bookMove, &first);
11704     }
11705 }
11706
11707
11708 void
11709 DisplayTwoMachinesTitle()
11710 {
11711     char buf[MSG_SIZ];
11712     if (appData.matchGames > 0) {
11713         if (first.twoMachinesColor[0] == 'w') {
11714             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11715                     gameInfo.white, gameInfo.black,
11716                     first.matchWins, second.matchWins,
11717                     matchGame - 1 - (first.matchWins + second.matchWins));
11718         } else {
11719             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11720                     gameInfo.white, gameInfo.black,
11721                     second.matchWins, first.matchWins,
11722                     matchGame - 1 - (first.matchWins + second.matchWins));
11723         }
11724     } else {
11725         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11726     }
11727     DisplayTitle(buf);
11728 }
11729
11730 void
11731 TwoMachinesEvent P((void))
11732 {
11733     int i;
11734     char buf[MSG_SIZ];
11735     ChessProgramState *onmove;
11736     char *bookHit = NULL;
11737     
11738     if (appData.noChessProgram) return;
11739
11740     switch (gameMode) {
11741       case TwoMachinesPlay:
11742         return;
11743       case MachinePlaysWhite:
11744       case MachinePlaysBlack:
11745         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11746             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11747             return;
11748         }
11749         /* fall through */
11750       case BeginningOfGame:
11751       case PlayFromGameFile:
11752       case EndOfGame:
11753         EditGameEvent();
11754         if (gameMode != EditGame) return;
11755         break;
11756       case EditPosition:
11757         EditPositionDone(TRUE);
11758         break;
11759       case AnalyzeMode:
11760       case AnalyzeFile:
11761         ExitAnalyzeMode();
11762         break;
11763       case EditGame:
11764       default:
11765         break;
11766     }
11767
11768 //    forwardMostMove = currentMove;
11769     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11770     ResurrectChessProgram();    /* in case first program isn't running */
11771
11772     if (second.pr == NULL) {
11773         StartChessProgram(&second);
11774         if (second.protocolVersion == 1) {
11775           TwoMachinesEventIfReady();
11776         } else {
11777           /* kludge: allow timeout for initial "feature" command */
11778           FreezeUI();
11779           DisplayMessage("", _("Starting second chess program"));
11780           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11781         }
11782         return;
11783     }
11784     DisplayMessage("", "");
11785     InitChessProgram(&second, FALSE);
11786     SendToProgram("force\n", &second);
11787     if (startedFromSetupPosition) {
11788         SendBoard(&second, backwardMostMove);
11789     if (appData.debugMode) {
11790         fprintf(debugFP, "Two Machines\n");
11791     }
11792     }
11793     for (i = backwardMostMove; i < forwardMostMove; i++) {
11794         SendMoveToProgram(i, &second);
11795     }
11796
11797     gameMode = TwoMachinesPlay;
11798     pausing = FALSE;
11799     ModeHighlight();
11800     SetGameInfo();
11801     DisplayTwoMachinesTitle();
11802     firstMove = TRUE;
11803     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11804         onmove = &first;
11805     } else {
11806         onmove = &second;
11807     }
11808
11809     SendToProgram(first.computerString, &first);
11810     if (first.sendName) {
11811       sprintf(buf, "name %s\n", second.tidy);
11812       SendToProgram(buf, &first);
11813     }
11814     SendToProgram(second.computerString, &second);
11815     if (second.sendName) {
11816       sprintf(buf, "name %s\n", first.tidy);
11817       SendToProgram(buf, &second);
11818     }
11819
11820     ResetClocks();
11821     if (!first.sendTime || !second.sendTime) {
11822         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11823         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11824     }
11825     if (onmove->sendTime) {
11826       if (onmove->useColors) {
11827         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11828       }
11829       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11830     }
11831     if (onmove->useColors) {
11832       SendToProgram(onmove->twoMachinesColor, onmove);
11833     }
11834     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11835 //    SendToProgram("go\n", onmove);
11836     onmove->maybeThinking = TRUE;
11837     SetMachineThinkingEnables();
11838
11839     StartClocks();
11840
11841     if(bookHit) { // [HGM] book: simulate book reply
11842         static char bookMove[MSG_SIZ]; // a bit generous?
11843
11844         programStats.nodes = programStats.depth = programStats.time = 
11845         programStats.score = programStats.got_only_move = 0;
11846         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11847
11848         strcpy(bookMove, "move ");
11849         strcat(bookMove, bookHit);
11850         savedMessage = bookMove; // args for deferred call
11851         savedState = onmove;
11852         ScheduleDelayedEvent(DeferredBookMove, 1);
11853     }
11854 }
11855
11856 void
11857 TrainingEvent()
11858 {
11859     if (gameMode == Training) {
11860       SetTrainingModeOff();
11861       gameMode = PlayFromGameFile;
11862       DisplayMessage("", _("Training mode off"));
11863     } else {
11864       gameMode = Training;
11865       animateTraining = appData.animate;
11866
11867       /* make sure we are not already at the end of the game */
11868       if (currentMove < forwardMostMove) {
11869         SetTrainingModeOn();
11870         DisplayMessage("", _("Training mode on"));
11871       } else {
11872         gameMode = PlayFromGameFile;
11873         DisplayError(_("Already at end of game"), 0);
11874       }
11875     }
11876     ModeHighlight();
11877 }
11878
11879 void
11880 IcsClientEvent()
11881 {
11882     if (!appData.icsActive) return;
11883     switch (gameMode) {
11884       case IcsPlayingWhite:
11885       case IcsPlayingBlack:
11886       case IcsObserving:
11887       case IcsIdle:
11888       case BeginningOfGame:
11889       case IcsExamining:
11890         return;
11891
11892       case EditGame:
11893         break;
11894
11895       case EditPosition:
11896         EditPositionDone(TRUE);
11897         break;
11898
11899       case AnalyzeMode:
11900       case AnalyzeFile:
11901         ExitAnalyzeMode();
11902         break;
11903         
11904       default:
11905         EditGameEvent();
11906         break;
11907     }
11908
11909     gameMode = IcsIdle;
11910     ModeHighlight();
11911     return;
11912 }
11913
11914
11915 void
11916 EditGameEvent()
11917 {
11918     int i;
11919
11920     switch (gameMode) {
11921       case Training:
11922         SetTrainingModeOff();
11923         break;
11924       case MachinePlaysWhite:
11925       case MachinePlaysBlack:
11926       case BeginningOfGame:
11927         SendToProgram("force\n", &first);
11928         SetUserThinkingEnables();
11929         break;
11930       case PlayFromGameFile:
11931         (void) StopLoadGameTimer();
11932         if (gameFileFP != NULL) {
11933             gameFileFP = NULL;
11934         }
11935         break;
11936       case EditPosition:
11937         EditPositionDone(TRUE);
11938         break;
11939       case AnalyzeMode:
11940       case AnalyzeFile:
11941         ExitAnalyzeMode();
11942         SendToProgram("force\n", &first);
11943         break;
11944       case TwoMachinesPlay:
11945         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11946         ResurrectChessProgram();
11947         SetUserThinkingEnables();
11948         break;
11949       case EndOfGame:
11950         ResurrectChessProgram();
11951         break;
11952       case IcsPlayingBlack:
11953       case IcsPlayingWhite:
11954         DisplayError(_("Warning: You are still playing a game"), 0);
11955         break;
11956       case IcsObserving:
11957         DisplayError(_("Warning: You are still observing a game"), 0);
11958         break;
11959       case IcsExamining:
11960         DisplayError(_("Warning: You are still examining a game"), 0);
11961         break;
11962       case IcsIdle:
11963         break;
11964       case EditGame:
11965       default:
11966         return;
11967     }
11968     
11969     pausing = FALSE;
11970     StopClocks();
11971     first.offeredDraw = second.offeredDraw = 0;
11972
11973     if (gameMode == PlayFromGameFile) {
11974         whiteTimeRemaining = timeRemaining[0][currentMove];
11975         blackTimeRemaining = timeRemaining[1][currentMove];
11976         DisplayTitle("");
11977     }
11978
11979     if (gameMode == MachinePlaysWhite ||
11980         gameMode == MachinePlaysBlack ||
11981         gameMode == TwoMachinesPlay ||
11982         gameMode == EndOfGame) {
11983         i = forwardMostMove;
11984         while (i > currentMove) {
11985             SendToProgram("undo\n", &first);
11986             i--;
11987         }
11988         whiteTimeRemaining = timeRemaining[0][currentMove];
11989         blackTimeRemaining = timeRemaining[1][currentMove];
11990         DisplayBothClocks();
11991         if (whiteFlag || blackFlag) {
11992             whiteFlag = blackFlag = 0;
11993         }
11994         DisplayTitle("");
11995     }           
11996     
11997     gameMode = EditGame;
11998     ModeHighlight();
11999     SetGameInfo();
12000 }
12001
12002
12003 void
12004 EditPositionEvent()
12005 {
12006     if (gameMode == EditPosition) {
12007         EditGameEvent();
12008         return;
12009     }
12010     
12011     EditGameEvent();
12012     if (gameMode != EditGame) return;
12013     
12014     gameMode = EditPosition;
12015     ModeHighlight();
12016     SetGameInfo();
12017     if (currentMove > 0)
12018       CopyBoard(boards[0], boards[currentMove]);
12019     
12020     blackPlaysFirst = !WhiteOnMove(currentMove);
12021     ResetClocks();
12022     currentMove = forwardMostMove = backwardMostMove = 0;
12023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12024     DisplayMove(-1);
12025 }
12026
12027 void
12028 ExitAnalyzeMode()
12029 {
12030     /* [DM] icsEngineAnalyze - possible call from other functions */
12031     if (appData.icsEngineAnalyze) {
12032         appData.icsEngineAnalyze = FALSE;
12033
12034         DisplayMessage("",_("Close ICS engine analyze..."));
12035     }
12036     if (first.analysisSupport && first.analyzing) {
12037       SendToProgram("exit\n", &first);
12038       first.analyzing = FALSE;
12039     }
12040     thinkOutput[0] = NULLCHAR;
12041 }
12042
12043 void
12044 EditPositionDone(Boolean fakeRights)
12045 {
12046     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12047
12048     startedFromSetupPosition = TRUE;
12049     InitChessProgram(&first, FALSE);
12050     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12051       boards[0][EP_STATUS] = EP_NONE;
12052       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12053     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12054         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12055         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12056       } else boards[0][CASTLING][2] = NoRights;
12057     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12058         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12059         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12060       } else boards[0][CASTLING][5] = NoRights;
12061     }
12062     SendToProgram("force\n", &first);
12063     if (blackPlaysFirst) {
12064         strcpy(moveList[0], "");
12065         strcpy(parseList[0], "");
12066         currentMove = forwardMostMove = backwardMostMove = 1;
12067         CopyBoard(boards[1], boards[0]);
12068     } else {
12069         currentMove = forwardMostMove = backwardMostMove = 0;
12070     }
12071     SendBoard(&first, forwardMostMove);
12072     if (appData.debugMode) {
12073         fprintf(debugFP, "EditPosDone\n");
12074     }
12075     DisplayTitle("");
12076     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12077     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12078     gameMode = EditGame;
12079     ModeHighlight();
12080     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12081     ClearHighlights(); /* [AS] */
12082 }
12083
12084 /* Pause for `ms' milliseconds */
12085 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12086 void
12087 TimeDelay(ms)
12088      long ms;
12089 {
12090     TimeMark m1, m2;
12091
12092     GetTimeMark(&m1);
12093     do {
12094         GetTimeMark(&m2);
12095     } while (SubtractTimeMarks(&m2, &m1) < ms);
12096 }
12097
12098 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12099 void
12100 SendMultiLineToICS(buf)
12101      char *buf;
12102 {
12103     char temp[MSG_SIZ+1], *p;
12104     int len;
12105
12106     len = strlen(buf);
12107     if (len > MSG_SIZ)
12108       len = MSG_SIZ;
12109   
12110     strncpy(temp, buf, len);
12111     temp[len] = 0;
12112
12113     p = temp;
12114     while (*p) {
12115         if (*p == '\n' || *p == '\r')
12116           *p = ' ';
12117         ++p;
12118     }
12119
12120     strcat(temp, "\n");
12121     SendToICS(temp);
12122     SendToPlayer(temp, strlen(temp));
12123 }
12124
12125 void
12126 SetWhiteToPlayEvent()
12127 {
12128     if (gameMode == EditPosition) {
12129         blackPlaysFirst = FALSE;
12130         DisplayBothClocks();    /* works because currentMove is 0 */
12131     } else if (gameMode == IcsExamining) {
12132         SendToICS(ics_prefix);
12133         SendToICS("tomove white\n");
12134     }
12135 }
12136
12137 void
12138 SetBlackToPlayEvent()
12139 {
12140     if (gameMode == EditPosition) {
12141         blackPlaysFirst = TRUE;
12142         currentMove = 1;        /* kludge */
12143         DisplayBothClocks();
12144         currentMove = 0;
12145     } else if (gameMode == IcsExamining) {
12146         SendToICS(ics_prefix);
12147         SendToICS("tomove black\n");
12148     }
12149 }
12150
12151 void
12152 EditPositionMenuEvent(selection, x, y)
12153      ChessSquare selection;
12154      int x, y;
12155 {
12156     char buf[MSG_SIZ];
12157     ChessSquare piece = boards[0][y][x];
12158
12159     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12160
12161     switch (selection) {
12162       case ClearBoard:
12163         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12164             SendToICS(ics_prefix);
12165             SendToICS("bsetup clear\n");
12166         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12167             SendToICS(ics_prefix);
12168             SendToICS("clearboard\n");
12169         } else {
12170             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12171                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12172                 for (y = 0; y < BOARD_HEIGHT; y++) {
12173                     if (gameMode == IcsExamining) {
12174                         if (boards[currentMove][y][x] != EmptySquare) {
12175                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12176                                     AAA + x, ONE + y);
12177                             SendToICS(buf);
12178                         }
12179                     } else {
12180                         boards[0][y][x] = p;
12181                     }
12182                 }
12183             }
12184         }
12185         if (gameMode == EditPosition) {
12186             DrawPosition(FALSE, boards[0]);
12187         }
12188         break;
12189
12190       case WhitePlay:
12191         SetWhiteToPlayEvent();
12192         break;
12193
12194       case BlackPlay:
12195         SetBlackToPlayEvent();
12196         break;
12197
12198       case EmptySquare:
12199         if (gameMode == IcsExamining) {
12200             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12201             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12202             SendToICS(buf);
12203         } else {
12204             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12205                 if(x == BOARD_LEFT-2) {
12206                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12207                     boards[0][y][1] = 0;
12208                 } else
12209                 if(x == BOARD_RGHT+1) {
12210                     if(y >= gameInfo.holdingsSize) break;
12211                     boards[0][y][BOARD_WIDTH-2] = 0;
12212                 } else break;
12213             }
12214             boards[0][y][x] = EmptySquare;
12215             DrawPosition(FALSE, boards[0]);
12216         }
12217         break;
12218
12219       case PromotePiece:
12220         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12221            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12222             selection = (ChessSquare) (PROMOTED piece);
12223         } else if(piece == EmptySquare) selection = WhiteSilver;
12224         else selection = (ChessSquare)((int)piece - 1);
12225         goto defaultlabel;
12226
12227       case DemotePiece:
12228         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12229            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12230             selection = (ChessSquare) (DEMOTED piece);
12231         } else if(piece == EmptySquare) selection = BlackSilver;
12232         else selection = (ChessSquare)((int)piece + 1);       
12233         goto defaultlabel;
12234
12235       case WhiteQueen:
12236       case BlackQueen:
12237         if(gameInfo.variant == VariantShatranj ||
12238            gameInfo.variant == VariantXiangqi  ||
12239            gameInfo.variant == VariantCourier  ||
12240            gameInfo.variant == VariantMakruk     )
12241             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12242         goto defaultlabel;
12243
12244       case WhiteKing:
12245       case BlackKing:
12246         if(gameInfo.variant == VariantXiangqi)
12247             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12248         if(gameInfo.variant == VariantKnightmate)
12249             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12250       default:
12251         defaultlabel:
12252         if (gameMode == IcsExamining) {
12253             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12254             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12255                     PieceToChar(selection), AAA + x, ONE + y);
12256             SendToICS(buf);
12257         } else {
12258             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12259                 int n;
12260                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12261                     n = PieceToNumber(selection - BlackPawn);
12262                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12263                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12264                     boards[0][BOARD_HEIGHT-1-n][1]++;
12265                 } else
12266                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12267                     n = PieceToNumber(selection);
12268                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12269                     boards[0][n][BOARD_WIDTH-1] = selection;
12270                     boards[0][n][BOARD_WIDTH-2]++;
12271                 }
12272             } else
12273             boards[0][y][x] = selection;
12274             DrawPosition(TRUE, boards[0]);
12275         }
12276         break;
12277     }
12278 }
12279
12280
12281 void
12282 DropMenuEvent(selection, x, y)
12283      ChessSquare selection;
12284      int x, y;
12285 {
12286     ChessMove moveType;
12287
12288     switch (gameMode) {
12289       case IcsPlayingWhite:
12290       case MachinePlaysBlack:
12291         if (!WhiteOnMove(currentMove)) {
12292             DisplayMoveError(_("It is Black's turn"));
12293             return;
12294         }
12295         moveType = WhiteDrop;
12296         break;
12297       case IcsPlayingBlack:
12298       case MachinePlaysWhite:
12299         if (WhiteOnMove(currentMove)) {
12300             DisplayMoveError(_("It is White's turn"));
12301             return;
12302         }
12303         moveType = BlackDrop;
12304         break;
12305       case EditGame:
12306         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12307         break;
12308       default:
12309         return;
12310     }
12311
12312     if (moveType == BlackDrop && selection < BlackPawn) {
12313       selection = (ChessSquare) ((int) selection
12314                                  + (int) BlackPawn - (int) WhitePawn);
12315     }
12316     if (boards[currentMove][y][x] != EmptySquare) {
12317         DisplayMoveError(_("That square is occupied"));
12318         return;
12319     }
12320
12321     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12322 }
12323
12324 void
12325 AcceptEvent()
12326 {
12327     /* Accept a pending offer of any kind from opponent */
12328     
12329     if (appData.icsActive) {
12330         SendToICS(ics_prefix);
12331         SendToICS("accept\n");
12332     } else if (cmailMsgLoaded) {
12333         if (currentMove == cmailOldMove &&
12334             commentList[cmailOldMove] != NULL &&
12335             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12336                    "Black offers a draw" : "White offers a draw")) {
12337             TruncateGame();
12338             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12339             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12340         } else {
12341             DisplayError(_("There is no pending offer on this move"), 0);
12342             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12343         }
12344     } else {
12345         /* Not used for offers from chess program */
12346     }
12347 }
12348
12349 void
12350 DeclineEvent()
12351 {
12352     /* Decline a pending offer of any kind from opponent */
12353     
12354     if (appData.icsActive) {
12355         SendToICS(ics_prefix);
12356         SendToICS("decline\n");
12357     } else if (cmailMsgLoaded) {
12358         if (currentMove == cmailOldMove &&
12359             commentList[cmailOldMove] != NULL &&
12360             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12361                    "Black offers a draw" : "White offers a draw")) {
12362 #ifdef NOTDEF
12363             AppendComment(cmailOldMove, "Draw declined", TRUE);
12364             DisplayComment(cmailOldMove - 1, "Draw declined");
12365 #endif /*NOTDEF*/
12366         } else {
12367             DisplayError(_("There is no pending offer on this move"), 0);
12368         }
12369     } else {
12370         /* Not used for offers from chess program */
12371     }
12372 }
12373
12374 void
12375 RematchEvent()
12376 {
12377     /* Issue ICS rematch command */
12378     if (appData.icsActive) {
12379         SendToICS(ics_prefix);
12380         SendToICS("rematch\n");
12381     }
12382 }
12383
12384 void
12385 CallFlagEvent()
12386 {
12387     /* Call your opponent's flag (claim a win on time) */
12388     if (appData.icsActive) {
12389         SendToICS(ics_prefix);
12390         SendToICS("flag\n");
12391     } else {
12392         switch (gameMode) {
12393           default:
12394             return;
12395           case MachinePlaysWhite:
12396             if (whiteFlag) {
12397                 if (blackFlag)
12398                   GameEnds(GameIsDrawn, "Both players ran out of time",
12399                            GE_PLAYER);
12400                 else
12401                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12402             } else {
12403                 DisplayError(_("Your opponent is not out of time"), 0);
12404             }
12405             break;
12406           case MachinePlaysBlack:
12407             if (blackFlag) {
12408                 if (whiteFlag)
12409                   GameEnds(GameIsDrawn, "Both players ran out of time",
12410                            GE_PLAYER);
12411                 else
12412                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12413             } else {
12414                 DisplayError(_("Your opponent is not out of time"), 0);
12415             }
12416             break;
12417         }
12418     }
12419 }
12420
12421 void
12422 DrawEvent()
12423 {
12424     /* Offer draw or accept pending draw offer from opponent */
12425     
12426     if (appData.icsActive) {
12427         /* Note: tournament rules require draw offers to be
12428            made after you make your move but before you punch
12429            your clock.  Currently ICS doesn't let you do that;
12430            instead, you immediately punch your clock after making
12431            a move, but you can offer a draw at any time. */
12432         
12433         SendToICS(ics_prefix);
12434         SendToICS("draw\n");
12435         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12436     } else if (cmailMsgLoaded) {
12437         if (currentMove == cmailOldMove &&
12438             commentList[cmailOldMove] != NULL &&
12439             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12440                    "Black offers a draw" : "White offers a draw")) {
12441             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12442             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12443         } else if (currentMove == cmailOldMove + 1) {
12444             char *offer = WhiteOnMove(cmailOldMove) ?
12445               "White offers a draw" : "Black offers a draw";
12446             AppendComment(currentMove, offer, TRUE);
12447             DisplayComment(currentMove - 1, offer);
12448             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12449         } else {
12450             DisplayError(_("You must make your move before offering a draw"), 0);
12451             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12452         }
12453     } else if (first.offeredDraw) {
12454         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12455     } else {
12456         if (first.sendDrawOffers) {
12457             SendToProgram("draw\n", &first);
12458             userOfferedDraw = TRUE;
12459         }
12460     }
12461 }
12462
12463 void
12464 AdjournEvent()
12465 {
12466     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12467     
12468     if (appData.icsActive) {
12469         SendToICS(ics_prefix);
12470         SendToICS("adjourn\n");
12471     } else {
12472         /* Currently GNU Chess doesn't offer or accept Adjourns */
12473     }
12474 }
12475
12476
12477 void
12478 AbortEvent()
12479 {
12480     /* Offer Abort or accept pending Abort offer from opponent */
12481     
12482     if (appData.icsActive) {
12483         SendToICS(ics_prefix);
12484         SendToICS("abort\n");
12485     } else {
12486         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12487     }
12488 }
12489
12490 void
12491 ResignEvent()
12492 {
12493     /* Resign.  You can do this even if it's not your turn. */
12494     
12495     if (appData.icsActive) {
12496         SendToICS(ics_prefix);
12497         SendToICS("resign\n");
12498     } else {
12499         switch (gameMode) {
12500           case MachinePlaysWhite:
12501             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12502             break;
12503           case MachinePlaysBlack:
12504             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12505             break;
12506           case EditGame:
12507             if (cmailMsgLoaded) {
12508                 TruncateGame();
12509                 if (WhiteOnMove(cmailOldMove)) {
12510                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12511                 } else {
12512                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12513                 }
12514                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12515             }
12516             break;
12517           default:
12518             break;
12519         }
12520     }
12521 }
12522
12523
12524 void
12525 StopObservingEvent()
12526 {
12527     /* Stop observing current games */
12528     SendToICS(ics_prefix);
12529     SendToICS("unobserve\n");
12530 }
12531
12532 void
12533 StopExaminingEvent()
12534 {
12535     /* Stop observing current game */
12536     SendToICS(ics_prefix);
12537     SendToICS("unexamine\n");
12538 }
12539
12540 void
12541 ForwardInner(target)
12542      int target;
12543 {
12544     int limit;
12545
12546     if (appData.debugMode)
12547         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12548                 target, currentMove, forwardMostMove);
12549
12550     if (gameMode == EditPosition)
12551       return;
12552
12553     if (gameMode == PlayFromGameFile && !pausing)
12554       PauseEvent();
12555     
12556     if (gameMode == IcsExamining && pausing)
12557       limit = pauseExamForwardMostMove;
12558     else
12559       limit = forwardMostMove;
12560     
12561     if (target > limit) target = limit;
12562
12563     if (target > 0 && moveList[target - 1][0]) {
12564         int fromX, fromY, toX, toY;
12565         toX = moveList[target - 1][2] - AAA;
12566         toY = moveList[target - 1][3] - ONE;
12567         if (moveList[target - 1][1] == '@') {
12568             if (appData.highlightLastMove) {
12569                 SetHighlights(-1, -1, toX, toY);
12570             }
12571         } else {
12572             fromX = moveList[target - 1][0] - AAA;
12573             fromY = moveList[target - 1][1] - ONE;
12574             if (target == currentMove + 1) {
12575                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12576             }
12577             if (appData.highlightLastMove) {
12578                 SetHighlights(fromX, fromY, toX, toY);
12579             }
12580         }
12581     }
12582     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12583         gameMode == Training || gameMode == PlayFromGameFile || 
12584         gameMode == AnalyzeFile) {
12585         while (currentMove < target) {
12586             SendMoveToProgram(currentMove++, &first);
12587         }
12588     } else {
12589         currentMove = target;
12590     }
12591     
12592     if (gameMode == EditGame || gameMode == EndOfGame) {
12593         whiteTimeRemaining = timeRemaining[0][currentMove];
12594         blackTimeRemaining = timeRemaining[1][currentMove];
12595     }
12596     DisplayBothClocks();
12597     DisplayMove(currentMove - 1);
12598     DrawPosition(FALSE, boards[currentMove]);
12599     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12600     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12601         DisplayComment(currentMove - 1, commentList[currentMove]);
12602     }
12603 }
12604
12605
12606 void
12607 ForwardEvent()
12608 {
12609     if (gameMode == IcsExamining && !pausing) {
12610         SendToICS(ics_prefix);
12611         SendToICS("forward\n");
12612     } else {
12613         ForwardInner(currentMove + 1);
12614     }
12615 }
12616
12617 void
12618 ToEndEvent()
12619 {
12620     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12621         /* to optimze, we temporarily turn off analysis mode while we feed
12622          * the remaining moves to the engine. Otherwise we get analysis output
12623          * after each move.
12624          */ 
12625         if (first.analysisSupport) {
12626           SendToProgram("exit\nforce\n", &first);
12627           first.analyzing = FALSE;
12628         }
12629     }
12630         
12631     if (gameMode == IcsExamining && !pausing) {
12632         SendToICS(ics_prefix);
12633         SendToICS("forward 999999\n");
12634     } else {
12635         ForwardInner(forwardMostMove);
12636     }
12637
12638     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12639         /* we have fed all the moves, so reactivate analysis mode */
12640         SendToProgram("analyze\n", &first);
12641         first.analyzing = TRUE;
12642         /*first.maybeThinking = TRUE;*/
12643         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12644     }
12645 }
12646
12647 void
12648 BackwardInner(target)
12649      int target;
12650 {
12651     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12652
12653     if (appData.debugMode)
12654         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12655                 target, currentMove, forwardMostMove);
12656
12657     if (gameMode == EditPosition) return;
12658     if (currentMove <= backwardMostMove) {
12659         ClearHighlights();
12660         DrawPosition(full_redraw, boards[currentMove]);
12661         return;
12662     }
12663     if (gameMode == PlayFromGameFile && !pausing)
12664       PauseEvent();
12665     
12666     if (moveList[target][0]) {
12667         int fromX, fromY, toX, toY;
12668         toX = moveList[target][2] - AAA;
12669         toY = moveList[target][3] - ONE;
12670         if (moveList[target][1] == '@') {
12671             if (appData.highlightLastMove) {
12672                 SetHighlights(-1, -1, toX, toY);
12673             }
12674         } else {
12675             fromX = moveList[target][0] - AAA;
12676             fromY = moveList[target][1] - ONE;
12677             if (target == currentMove - 1) {
12678                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12679             }
12680             if (appData.highlightLastMove) {
12681                 SetHighlights(fromX, fromY, toX, toY);
12682             }
12683         }
12684     }
12685     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12686         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12687         while (currentMove > target) {
12688             SendToProgram("undo\n", &first);
12689             currentMove--;
12690         }
12691     } else {
12692         currentMove = target;
12693     }
12694     
12695     if (gameMode == EditGame || gameMode == EndOfGame) {
12696         whiteTimeRemaining = timeRemaining[0][currentMove];
12697         blackTimeRemaining = timeRemaining[1][currentMove];
12698     }
12699     DisplayBothClocks();
12700     DisplayMove(currentMove - 1);
12701     DrawPosition(full_redraw, boards[currentMove]);
12702     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12703     // [HGM] PV info: routine tests if comment empty
12704     DisplayComment(currentMove - 1, commentList[currentMove]);
12705 }
12706
12707 void
12708 BackwardEvent()
12709 {
12710     if (gameMode == IcsExamining && !pausing) {
12711         SendToICS(ics_prefix);
12712         SendToICS("backward\n");
12713     } else {
12714         BackwardInner(currentMove - 1);
12715     }
12716 }
12717
12718 void
12719 ToStartEvent()
12720 {
12721     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12722         /* to optimize, we temporarily turn off analysis mode while we undo
12723          * all the moves. Otherwise we get analysis output after each undo.
12724          */ 
12725         if (first.analysisSupport) {
12726           SendToProgram("exit\nforce\n", &first);
12727           first.analyzing = FALSE;
12728         }
12729     }
12730
12731     if (gameMode == IcsExamining && !pausing) {
12732         SendToICS(ics_prefix);
12733         SendToICS("backward 999999\n");
12734     } else {
12735         BackwardInner(backwardMostMove);
12736     }
12737
12738     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12739         /* we have fed all the moves, so reactivate analysis mode */
12740         SendToProgram("analyze\n", &first);
12741         first.analyzing = TRUE;
12742         /*first.maybeThinking = TRUE;*/
12743         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12744     }
12745 }
12746
12747 void
12748 ToNrEvent(int to)
12749 {
12750   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12751   if (to >= forwardMostMove) to = forwardMostMove;
12752   if (to <= backwardMostMove) to = backwardMostMove;
12753   if (to < currentMove) {
12754     BackwardInner(to);
12755   } else {
12756     ForwardInner(to);
12757   }
12758 }
12759
12760 void
12761 RevertEvent(Boolean annotate)
12762 {
12763     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12764         return;
12765     }
12766     if (gameMode != IcsExamining) {
12767         DisplayError(_("You are not examining a game"), 0);
12768         return;
12769     }
12770     if (pausing) {
12771         DisplayError(_("You can't revert while pausing"), 0);
12772         return;
12773     }
12774     SendToICS(ics_prefix);
12775     SendToICS("revert\n");
12776 }
12777
12778 void
12779 RetractMoveEvent()
12780 {
12781     switch (gameMode) {
12782       case MachinePlaysWhite:
12783       case MachinePlaysBlack:
12784         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12785             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12786             return;
12787         }
12788         if (forwardMostMove < 2) return;
12789         currentMove = forwardMostMove = forwardMostMove - 2;
12790         whiteTimeRemaining = timeRemaining[0][currentMove];
12791         blackTimeRemaining = timeRemaining[1][currentMove];
12792         DisplayBothClocks();
12793         DisplayMove(currentMove - 1);
12794         ClearHighlights();/*!! could figure this out*/
12795         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12796         SendToProgram("remove\n", &first);
12797         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12798         break;
12799
12800       case BeginningOfGame:
12801       default:
12802         break;
12803
12804       case IcsPlayingWhite:
12805       case IcsPlayingBlack:
12806         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12807             SendToICS(ics_prefix);
12808             SendToICS("takeback 2\n");
12809         } else {
12810             SendToICS(ics_prefix);
12811             SendToICS("takeback 1\n");
12812         }
12813         break;
12814     }
12815 }
12816
12817 void
12818 MoveNowEvent()
12819 {
12820     ChessProgramState *cps;
12821
12822     switch (gameMode) {
12823       case MachinePlaysWhite:
12824         if (!WhiteOnMove(forwardMostMove)) {
12825             DisplayError(_("It is your turn"), 0);
12826             return;
12827         }
12828         cps = &first;
12829         break;
12830       case MachinePlaysBlack:
12831         if (WhiteOnMove(forwardMostMove)) {
12832             DisplayError(_("It is your turn"), 0);
12833             return;
12834         }
12835         cps = &first;
12836         break;
12837       case TwoMachinesPlay:
12838         if (WhiteOnMove(forwardMostMove) ==
12839             (first.twoMachinesColor[0] == 'w')) {
12840             cps = &first;
12841         } else {
12842             cps = &second;
12843         }
12844         break;
12845       case BeginningOfGame:
12846       default:
12847         return;
12848     }
12849     SendToProgram("?\n", cps);
12850 }
12851
12852 void
12853 TruncateGameEvent()
12854 {
12855     EditGameEvent();
12856     if (gameMode != EditGame) return;
12857     TruncateGame();
12858 }
12859
12860 void
12861 TruncateGame()
12862 {
12863     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12864     if (forwardMostMove > currentMove) {
12865         if (gameInfo.resultDetails != NULL) {
12866             free(gameInfo.resultDetails);
12867             gameInfo.resultDetails = NULL;
12868             gameInfo.result = GameUnfinished;
12869         }
12870         forwardMostMove = currentMove;
12871         HistorySet(parseList, backwardMostMove, forwardMostMove,
12872                    currentMove-1);
12873     }
12874 }
12875
12876 void
12877 HintEvent()
12878 {
12879     if (appData.noChessProgram) return;
12880     switch (gameMode) {
12881       case MachinePlaysWhite:
12882         if (WhiteOnMove(forwardMostMove)) {
12883             DisplayError(_("Wait until your turn"), 0);
12884             return;
12885         }
12886         break;
12887       case BeginningOfGame:
12888       case MachinePlaysBlack:
12889         if (!WhiteOnMove(forwardMostMove)) {
12890             DisplayError(_("Wait until your turn"), 0);
12891             return;
12892         }
12893         break;
12894       default:
12895         DisplayError(_("No hint available"), 0);
12896         return;
12897     }
12898     SendToProgram("hint\n", &first);
12899     hintRequested = TRUE;
12900 }
12901
12902 void
12903 BookEvent()
12904 {
12905     if (appData.noChessProgram) return;
12906     switch (gameMode) {
12907       case MachinePlaysWhite:
12908         if (WhiteOnMove(forwardMostMove)) {
12909             DisplayError(_("Wait until your turn"), 0);
12910             return;
12911         }
12912         break;
12913       case BeginningOfGame:
12914       case MachinePlaysBlack:
12915         if (!WhiteOnMove(forwardMostMove)) {
12916             DisplayError(_("Wait until your turn"), 0);
12917             return;
12918         }
12919         break;
12920       case EditPosition:
12921         EditPositionDone(TRUE);
12922         break;
12923       case TwoMachinesPlay:
12924         return;
12925       default:
12926         break;
12927     }
12928     SendToProgram("bk\n", &first);
12929     bookOutput[0] = NULLCHAR;
12930     bookRequested = TRUE;
12931 }
12932
12933 void
12934 AboutGameEvent()
12935 {
12936     char *tags = PGNTags(&gameInfo);
12937     TagsPopUp(tags, CmailMsg());
12938     free(tags);
12939 }
12940
12941 /* end button procedures */
12942
12943 void
12944 PrintPosition(fp, move)
12945      FILE *fp;
12946      int move;
12947 {
12948     int i, j;
12949     
12950     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12951         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12952             char c = PieceToChar(boards[move][i][j]);
12953             fputc(c == 'x' ? '.' : c, fp);
12954             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12955         }
12956     }
12957     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12958       fprintf(fp, "white to play\n");
12959     else
12960       fprintf(fp, "black to play\n");
12961 }
12962
12963 void
12964 PrintOpponents(fp)
12965      FILE *fp;
12966 {
12967     if (gameInfo.white != NULL) {
12968         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12969     } else {
12970         fprintf(fp, "\n");
12971     }
12972 }
12973
12974 /* Find last component of program's own name, using some heuristics */
12975 void
12976 TidyProgramName(prog, host, buf)
12977      char *prog, *host, buf[MSG_SIZ];
12978 {
12979     char *p, *q;
12980     int local = (strcmp(host, "localhost") == 0);
12981     while (!local && (p = strchr(prog, ';')) != NULL) {
12982         p++;
12983         while (*p == ' ') p++;
12984         prog = p;
12985     }
12986     if (*prog == '"' || *prog == '\'') {
12987         q = strchr(prog + 1, *prog);
12988     } else {
12989         q = strchr(prog, ' ');
12990     }
12991     if (q == NULL) q = prog + strlen(prog);
12992     p = q;
12993     while (p >= prog && *p != '/' && *p != '\\') p--;
12994     p++;
12995     if(p == prog && *p == '"') p++;
12996     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12997     memcpy(buf, p, q - p);
12998     buf[q - p] = NULLCHAR;
12999     if (!local) {
13000         strcat(buf, "@");
13001         strcat(buf, host);
13002     }
13003 }
13004
13005 char *
13006 TimeControlTagValue()
13007 {
13008     char buf[MSG_SIZ];
13009     if (!appData.clockMode) {
13010         strcpy(buf, "-");
13011     } else if (movesPerSession > 0) {
13012         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13013     } else if (timeIncrement == 0) {
13014         sprintf(buf, "%ld", timeControl/1000);
13015     } else {
13016         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13017     }
13018     return StrSave(buf);
13019 }
13020
13021 void
13022 SetGameInfo()
13023 {
13024     /* This routine is used only for certain modes */
13025     VariantClass v = gameInfo.variant;
13026     ChessMove r = GameUnfinished;
13027     char *p = NULL;
13028
13029     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13030         r = gameInfo.result; 
13031         p = gameInfo.resultDetails; 
13032         gameInfo.resultDetails = NULL;
13033     }
13034     ClearGameInfo(&gameInfo);
13035     gameInfo.variant = v;
13036
13037     switch (gameMode) {
13038       case MachinePlaysWhite:
13039         gameInfo.event = StrSave( appData.pgnEventHeader );
13040         gameInfo.site = StrSave(HostName());
13041         gameInfo.date = PGNDate();
13042         gameInfo.round = StrSave("-");
13043         gameInfo.white = StrSave(first.tidy);
13044         gameInfo.black = StrSave(UserName());
13045         gameInfo.timeControl = TimeControlTagValue();
13046         break;
13047
13048       case MachinePlaysBlack:
13049         gameInfo.event = StrSave( appData.pgnEventHeader );
13050         gameInfo.site = StrSave(HostName());
13051         gameInfo.date = PGNDate();
13052         gameInfo.round = StrSave("-");
13053         gameInfo.white = StrSave(UserName());
13054         gameInfo.black = StrSave(first.tidy);
13055         gameInfo.timeControl = TimeControlTagValue();
13056         break;
13057
13058       case TwoMachinesPlay:
13059         gameInfo.event = StrSave( appData.pgnEventHeader );
13060         gameInfo.site = StrSave(HostName());
13061         gameInfo.date = PGNDate();
13062         if (matchGame > 0) {
13063             char buf[MSG_SIZ];
13064             sprintf(buf, "%d", matchGame);
13065             gameInfo.round = StrSave(buf);
13066         } else {
13067             gameInfo.round = StrSave("-");
13068         }
13069         if (first.twoMachinesColor[0] == 'w') {
13070             gameInfo.white = StrSave(first.tidy);
13071             gameInfo.black = StrSave(second.tidy);
13072         } else {
13073             gameInfo.white = StrSave(second.tidy);
13074             gameInfo.black = StrSave(first.tidy);
13075         }
13076         gameInfo.timeControl = TimeControlTagValue();
13077         break;
13078
13079       case EditGame:
13080         gameInfo.event = StrSave("Edited game");
13081         gameInfo.site = StrSave(HostName());
13082         gameInfo.date = PGNDate();
13083         gameInfo.round = StrSave("-");
13084         gameInfo.white = StrSave("-");
13085         gameInfo.black = StrSave("-");
13086         gameInfo.result = r;
13087         gameInfo.resultDetails = p;
13088         break;
13089
13090       case EditPosition:
13091         gameInfo.event = StrSave("Edited position");
13092         gameInfo.site = StrSave(HostName());
13093         gameInfo.date = PGNDate();
13094         gameInfo.round = StrSave("-");
13095         gameInfo.white = StrSave("-");
13096         gameInfo.black = StrSave("-");
13097         break;
13098
13099       case IcsPlayingWhite:
13100       case IcsPlayingBlack:
13101       case IcsObserving:
13102       case IcsExamining:
13103         break;
13104
13105       case PlayFromGameFile:
13106         gameInfo.event = StrSave("Game from non-PGN file");
13107         gameInfo.site = StrSave(HostName());
13108         gameInfo.date = PGNDate();
13109         gameInfo.round = StrSave("-");
13110         gameInfo.white = StrSave("?");
13111         gameInfo.black = StrSave("?");
13112         break;
13113
13114       default:
13115         break;
13116     }
13117 }
13118
13119 void
13120 ReplaceComment(index, text)
13121      int index;
13122      char *text;
13123 {
13124     int len;
13125
13126     while (*text == '\n') text++;
13127     len = strlen(text);
13128     while (len > 0 && text[len - 1] == '\n') len--;
13129
13130     if (commentList[index] != NULL)
13131       free(commentList[index]);
13132
13133     if (len == 0) {
13134         commentList[index] = NULL;
13135         return;
13136     }
13137   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13138       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13139       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13140     commentList[index] = (char *) malloc(len + 2);
13141     strncpy(commentList[index], text, len);
13142     commentList[index][len] = '\n';
13143     commentList[index][len + 1] = NULLCHAR;
13144   } else { 
13145     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13146     char *p;
13147     commentList[index] = (char *) malloc(len + 6);
13148     strcpy(commentList[index], "{\n");
13149     strncpy(commentList[index]+2, text, len);
13150     commentList[index][len+2] = NULLCHAR;
13151     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13152     strcat(commentList[index], "\n}\n");
13153   }
13154 }
13155
13156 void
13157 CrushCRs(text)
13158      char *text;
13159 {
13160   char *p = text;
13161   char *q = text;
13162   char ch;
13163
13164   do {
13165     ch = *p++;
13166     if (ch == '\r') continue;
13167     *q++ = ch;
13168   } while (ch != '\0');
13169 }
13170
13171 void
13172 AppendComment(index, text, addBraces)
13173      int index;
13174      char *text;
13175      Boolean addBraces; // [HGM] braces: tells if we should add {}
13176 {
13177     int oldlen, len;
13178     char *old;
13179
13180 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13181     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13182
13183     CrushCRs(text);
13184     while (*text == '\n') text++;
13185     len = strlen(text);
13186     while (len > 0 && text[len - 1] == '\n') len--;
13187
13188     if (len == 0) return;
13189
13190     if (commentList[index] != NULL) {
13191         old = commentList[index];
13192         oldlen = strlen(old);
13193         while(commentList[index][oldlen-1] ==  '\n')
13194           commentList[index][--oldlen] = NULLCHAR;
13195         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13196         strcpy(commentList[index], old);
13197         free(old);
13198         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13199         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13200           if(addBraces) addBraces = FALSE; else { text++; len--; }
13201           while (*text == '\n') { text++; len--; }
13202           commentList[index][--oldlen] = NULLCHAR;
13203       }
13204         if(addBraces) strcat(commentList[index], "\n{\n");
13205         else          strcat(commentList[index], "\n");
13206         strcat(commentList[index], text);
13207         if(addBraces) strcat(commentList[index], "\n}\n");
13208         else          strcat(commentList[index], "\n");
13209     } else {
13210         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13211         if(addBraces)
13212              strcpy(commentList[index], "{\n");
13213         else commentList[index][0] = NULLCHAR;
13214         strcat(commentList[index], text);
13215         strcat(commentList[index], "\n");
13216         if(addBraces) strcat(commentList[index], "}\n");
13217     }
13218 }
13219
13220 static char * FindStr( char * text, char * sub_text )
13221 {
13222     char * result = strstr( text, sub_text );
13223
13224     if( result != NULL ) {
13225         result += strlen( sub_text );
13226     }
13227
13228     return result;
13229 }
13230
13231 /* [AS] Try to extract PV info from PGN comment */
13232 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13233 char *GetInfoFromComment( int index, char * text )
13234 {
13235     char * sep = text;
13236
13237     if( text != NULL && index > 0 ) {
13238         int score = 0;
13239         int depth = 0;
13240         int time = -1, sec = 0, deci;
13241         char * s_eval = FindStr( text, "[%eval " );
13242         char * s_emt = FindStr( text, "[%emt " );
13243
13244         if( s_eval != NULL || s_emt != NULL ) {
13245             /* New style */
13246             char delim;
13247
13248             if( s_eval != NULL ) {
13249                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13250                     return text;
13251                 }
13252
13253                 if( delim != ']' ) {
13254                     return text;
13255                 }
13256             }
13257
13258             if( s_emt != NULL ) {
13259             }
13260                 return text;
13261         }
13262         else {
13263             /* We expect something like: [+|-]nnn.nn/dd */
13264             int score_lo = 0;
13265
13266             if(*text != '{') return text; // [HGM] braces: must be normal comment
13267
13268             sep = strchr( text, '/' );
13269             if( sep == NULL || sep < (text+4) ) {
13270                 return text;
13271             }
13272
13273             time = -1; sec = -1; deci = -1;
13274             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13275                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13276                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13277                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13278                 return text;
13279             }
13280
13281             if( score_lo < 0 || score_lo >= 100 ) {
13282                 return text;
13283             }
13284
13285             if(sec >= 0) time = 600*time + 10*sec; else
13286             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13287
13288             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13289
13290             /* [HGM] PV time: now locate end of PV info */
13291             while( *++sep >= '0' && *sep <= '9'); // strip depth
13292             if(time >= 0)
13293             while( *++sep >= '0' && *sep <= '9'); // strip time
13294             if(sec >= 0)
13295             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13296             if(deci >= 0)
13297             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13298             while(*sep == ' ') sep++;
13299         }
13300
13301         if( depth <= 0 ) {
13302             return text;
13303         }
13304
13305         if( time < 0 ) {
13306             time = -1;
13307         }
13308
13309         pvInfoList[index-1].depth = depth;
13310         pvInfoList[index-1].score = score;
13311         pvInfoList[index-1].time  = 10*time; // centi-sec
13312         if(*sep == '}') *sep = 0; else *--sep = '{';
13313     }
13314     return sep;
13315 }
13316
13317 void
13318 SendToProgram(message, cps)
13319      char *message;
13320      ChessProgramState *cps;
13321 {
13322     int count, outCount, error;
13323     char buf[MSG_SIZ];
13324
13325     if (cps->pr == NULL) return;
13326     Attention(cps);
13327     
13328     if (appData.debugMode) {
13329         TimeMark now;
13330         GetTimeMark(&now);
13331         fprintf(debugFP, "%ld >%-6s: %s", 
13332                 SubtractTimeMarks(&now, &programStartTime),
13333                 cps->which, message);
13334     }
13335     
13336     count = strlen(message);
13337     outCount = OutputToProcess(cps->pr, message, count, &error);
13338     if (outCount < count && !exiting 
13339                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13340         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13341         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13342             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13343                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13344                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13345             } else {
13346                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13347             }
13348             gameInfo.resultDetails = StrSave(buf);
13349         }
13350         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13351     }
13352 }
13353
13354 void
13355 ReceiveFromProgram(isr, closure, message, count, error)
13356      InputSourceRef isr;
13357      VOIDSTAR closure;
13358      char *message;
13359      int count;
13360      int error;
13361 {
13362     char *end_str;
13363     char buf[MSG_SIZ];
13364     ChessProgramState *cps = (ChessProgramState *)closure;
13365
13366     if (isr != cps->isr) return; /* Killed intentionally */
13367     if (count <= 0) {
13368         if (count == 0) {
13369             sprintf(buf,
13370                     _("Error: %s chess program (%s) exited unexpectedly"),
13371                     cps->which, cps->program);
13372         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13373                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13374                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13375                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13376                 } else {
13377                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13378                 }
13379                 gameInfo.resultDetails = StrSave(buf);
13380             }
13381             RemoveInputSource(cps->isr);
13382             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13383         } else {
13384             sprintf(buf,
13385                     _("Error reading from %s chess program (%s)"),
13386                     cps->which, cps->program);
13387             RemoveInputSource(cps->isr);
13388
13389             /* [AS] Program is misbehaving badly... kill it */
13390             if( count == -2 ) {
13391                 DestroyChildProcess( cps->pr, 9 );
13392                 cps->pr = NoProc;
13393             }
13394
13395             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13396         }
13397         return;
13398     }
13399     
13400     if ((end_str = strchr(message, '\r')) != NULL)
13401       *end_str = NULLCHAR;
13402     if ((end_str = strchr(message, '\n')) != NULL)
13403       *end_str = NULLCHAR;
13404     
13405     if (appData.debugMode) {
13406         TimeMark now; int print = 1;
13407         char *quote = ""; char c; int i;
13408
13409         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13410                 char start = message[0];
13411                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13412                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13413                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13414                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13415                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13416                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13417                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13418                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13419                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13420                     print = (appData.engineComments >= 2);
13421                 }
13422                 message[0] = start; // restore original message
13423         }
13424         if(print) {
13425                 GetTimeMark(&now);
13426                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13427                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13428                         quote,
13429                         message);
13430         }
13431     }
13432
13433     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13434     if (appData.icsEngineAnalyze) {
13435         if (strstr(message, "whisper") != NULL ||
13436              strstr(message, "kibitz") != NULL || 
13437             strstr(message, "tellics") != NULL) return;
13438     }
13439
13440     HandleMachineMove(message, cps);
13441 }
13442
13443
13444 void
13445 SendTimeControl(cps, mps, tc, inc, sd, st)
13446      ChessProgramState *cps;
13447      int mps, inc, sd, st;
13448      long tc;
13449 {
13450     char buf[MSG_SIZ];
13451     int seconds;
13452
13453     if( timeControl_2 > 0 ) {
13454         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13455             tc = timeControl_2;
13456         }
13457     }
13458     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13459     inc /= cps->timeOdds;
13460     st  /= cps->timeOdds;
13461
13462     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13463
13464     if (st > 0) {
13465       /* Set exact time per move, normally using st command */
13466       if (cps->stKludge) {
13467         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13468         seconds = st % 60;
13469         if (seconds == 0) {
13470           sprintf(buf, "level 1 %d\n", st/60);
13471         } else {
13472           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13473         }
13474       } else {
13475         sprintf(buf, "st %d\n", st);
13476       }
13477     } else {
13478       /* Set conventional or incremental time control, using level command */
13479       if (seconds == 0) {
13480         /* Note old gnuchess bug -- minutes:seconds used to not work.
13481            Fixed in later versions, but still avoid :seconds
13482            when seconds is 0. */
13483         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13484       } else {
13485         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13486                 seconds, inc/1000);
13487       }
13488     }
13489     SendToProgram(buf, cps);
13490
13491     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13492     /* Orthogonally, limit search to given depth */
13493     if (sd > 0) {
13494       if (cps->sdKludge) {
13495         sprintf(buf, "depth\n%d\n", sd);
13496       } else {
13497         sprintf(buf, "sd %d\n", sd);
13498       }
13499       SendToProgram(buf, cps);
13500     }
13501
13502     if(cps->nps > 0) { /* [HGM] nps */
13503         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13504         else {
13505                 sprintf(buf, "nps %d\n", cps->nps);
13506               SendToProgram(buf, cps);
13507         }
13508     }
13509 }
13510
13511 ChessProgramState *WhitePlayer()
13512 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13513 {
13514     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13515        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13516         return &second;
13517     return &first;
13518 }
13519
13520 void
13521 SendTimeRemaining(cps, machineWhite)
13522      ChessProgramState *cps;
13523      int /*boolean*/ machineWhite;
13524 {
13525     char message[MSG_SIZ];
13526     long time, otime;
13527
13528     /* Note: this routine must be called when the clocks are stopped
13529        or when they have *just* been set or switched; otherwise
13530        it will be off by the time since the current tick started.
13531     */
13532     if (machineWhite) {
13533         time = whiteTimeRemaining / 10;
13534         otime = blackTimeRemaining / 10;
13535     } else {
13536         time = blackTimeRemaining / 10;
13537         otime = whiteTimeRemaining / 10;
13538     }
13539     /* [HGM] translate opponent's time by time-odds factor */
13540     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13541     if (appData.debugMode) {
13542         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13543     }
13544
13545     if (time <= 0) time = 1;
13546     if (otime <= 0) otime = 1;
13547     
13548     sprintf(message, "time %ld\n", time);
13549     SendToProgram(message, cps);
13550
13551     sprintf(message, "otim %ld\n", otime);
13552     SendToProgram(message, cps);
13553 }
13554
13555 int
13556 BoolFeature(p, name, loc, cps)
13557      char **p;
13558      char *name;
13559      int *loc;
13560      ChessProgramState *cps;
13561 {
13562   char buf[MSG_SIZ];
13563   int len = strlen(name);
13564   int val;
13565   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13566     (*p) += len + 1;
13567     sscanf(*p, "%d", &val);
13568     *loc = (val != 0);
13569     while (**p && **p != ' ') (*p)++;
13570     sprintf(buf, "accepted %s\n", name);
13571     SendToProgram(buf, cps);
13572     return TRUE;
13573   }
13574   return FALSE;
13575 }
13576
13577 int
13578 IntFeature(p, name, loc, cps)
13579      char **p;
13580      char *name;
13581      int *loc;
13582      ChessProgramState *cps;
13583 {
13584   char buf[MSG_SIZ];
13585   int len = strlen(name);
13586   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13587     (*p) += len + 1;
13588     sscanf(*p, "%d", loc);
13589     while (**p && **p != ' ') (*p)++;
13590     sprintf(buf, "accepted %s\n", name);
13591     SendToProgram(buf, cps);
13592     return TRUE;
13593   }
13594   return FALSE;
13595 }
13596
13597 int
13598 StringFeature(p, name, loc, cps)
13599      char **p;
13600      char *name;
13601      char loc[];
13602      ChessProgramState *cps;
13603 {
13604   char buf[MSG_SIZ];
13605   int len = strlen(name);
13606   if (strncmp((*p), name, len) == 0
13607       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13608     (*p) += len + 2;
13609     sscanf(*p, "%[^\"]", loc);
13610     while (**p && **p != '\"') (*p)++;
13611     if (**p == '\"') (*p)++;
13612     sprintf(buf, "accepted %s\n", name);
13613     SendToProgram(buf, cps);
13614     return TRUE;
13615   }
13616   return FALSE;
13617 }
13618
13619 int 
13620 ParseOption(Option *opt, ChessProgramState *cps)
13621 // [HGM] options: process the string that defines an engine option, and determine
13622 // name, type, default value, and allowed value range
13623 {
13624         char *p, *q, buf[MSG_SIZ];
13625         int n, min = (-1)<<31, max = 1<<31, def;
13626
13627         if(p = strstr(opt->name, " -spin ")) {
13628             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13629             if(max < min) max = min; // enforce consistency
13630             if(def < min) def = min;
13631             if(def > max) def = max;
13632             opt->value = def;
13633             opt->min = min;
13634             opt->max = max;
13635             opt->type = Spin;
13636         } else if((p = strstr(opt->name, " -slider "))) {
13637             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13638             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13639             if(max < min) max = min; // enforce consistency
13640             if(def < min) def = min;
13641             if(def > max) def = max;
13642             opt->value = def;
13643             opt->min = min;
13644             opt->max = max;
13645             opt->type = Spin; // Slider;
13646         } else if((p = strstr(opt->name, " -string "))) {
13647             opt->textValue = p+9;
13648             opt->type = TextBox;
13649         } else if((p = strstr(opt->name, " -file "))) {
13650             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13651             opt->textValue = p+7;
13652             opt->type = TextBox; // FileName;
13653         } else if((p = strstr(opt->name, " -path "))) {
13654             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13655             opt->textValue = p+7;
13656             opt->type = TextBox; // PathName;
13657         } else if(p = strstr(opt->name, " -check ")) {
13658             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13659             opt->value = (def != 0);
13660             opt->type = CheckBox;
13661         } else if(p = strstr(opt->name, " -combo ")) {
13662             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13663             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13664             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13665             opt->value = n = 0;
13666             while(q = StrStr(q, " /// ")) {
13667                 n++; *q = 0;    // count choices, and null-terminate each of them
13668                 q += 5;
13669                 if(*q == '*') { // remember default, which is marked with * prefix
13670                     q++;
13671                     opt->value = n;
13672                 }
13673                 cps->comboList[cps->comboCnt++] = q;
13674             }
13675             cps->comboList[cps->comboCnt++] = NULL;
13676             opt->max = n + 1;
13677             opt->type = ComboBox;
13678         } else if(p = strstr(opt->name, " -button")) {
13679             opt->type = Button;
13680         } else if(p = strstr(opt->name, " -save")) {
13681             opt->type = SaveButton;
13682         } else return FALSE;
13683         *p = 0; // terminate option name
13684         // now look if the command-line options define a setting for this engine option.
13685         if(cps->optionSettings && cps->optionSettings[0])
13686             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13687         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13688                 sprintf(buf, "option %s", p);
13689                 if(p = strstr(buf, ",")) *p = 0;
13690                 strcat(buf, "\n");
13691                 SendToProgram(buf, cps);
13692         }
13693         return TRUE;
13694 }
13695
13696 void
13697 FeatureDone(cps, val)
13698      ChessProgramState* cps;
13699      int val;
13700 {
13701   DelayedEventCallback cb = GetDelayedEvent();
13702   if ((cb == InitBackEnd3 && cps == &first) ||
13703       (cb == TwoMachinesEventIfReady && cps == &second)) {
13704     CancelDelayedEvent();
13705     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13706   }
13707   cps->initDone = val;
13708 }
13709
13710 /* Parse feature command from engine */
13711 void
13712 ParseFeatures(args, cps)
13713      char* args;
13714      ChessProgramState *cps;  
13715 {
13716   char *p = args;
13717   char *q;
13718   int val;
13719   char buf[MSG_SIZ];
13720
13721   for (;;) {
13722     while (*p == ' ') p++;
13723     if (*p == NULLCHAR) return;
13724
13725     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13726     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13727     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13728     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13729     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13730     if (BoolFeature(&p, "reuse", &val, cps)) {
13731       /* Engine can disable reuse, but can't enable it if user said no */
13732       if (!val) cps->reuse = FALSE;
13733       continue;
13734     }
13735     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13736     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13737       if (gameMode == TwoMachinesPlay) {
13738         DisplayTwoMachinesTitle();
13739       } else {
13740         DisplayTitle("");
13741       }
13742       continue;
13743     }
13744     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13745     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13746     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13747     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13748     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13749     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13750     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13751     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13752     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13753     if (IntFeature(&p, "done", &val, cps)) {
13754       FeatureDone(cps, val);
13755       continue;
13756     }
13757     /* Added by Tord: */
13758     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13759     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13760     /* End of additions by Tord */
13761
13762     /* [HGM] added features: */
13763     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13764     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13765     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13766     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13767     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13768     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13769     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13770         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13771             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13772             SendToProgram(buf, cps);
13773             continue;
13774         }
13775         if(cps->nrOptions >= MAX_OPTIONS) {
13776             cps->nrOptions--;
13777             sprintf(buf, "%s engine has too many options\n", cps->which);
13778             DisplayError(buf, 0);
13779         }
13780         continue;
13781     }
13782     /* End of additions by HGM */
13783
13784     /* unknown feature: complain and skip */
13785     q = p;
13786     while (*q && *q != '=') q++;
13787     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13788     SendToProgram(buf, cps);
13789     p = q;
13790     if (*p == '=') {
13791       p++;
13792       if (*p == '\"') {
13793         p++;
13794         while (*p && *p != '\"') p++;
13795         if (*p == '\"') p++;
13796       } else {
13797         while (*p && *p != ' ') p++;
13798       }
13799     }
13800   }
13801
13802 }
13803
13804 void
13805 PeriodicUpdatesEvent(newState)
13806      int newState;
13807 {
13808     if (newState == appData.periodicUpdates)
13809       return;
13810
13811     appData.periodicUpdates=newState;
13812
13813     /* Display type changes, so update it now */
13814 //    DisplayAnalysis();
13815
13816     /* Get the ball rolling again... */
13817     if (newState) {
13818         AnalysisPeriodicEvent(1);
13819         StartAnalysisClock();
13820     }
13821 }
13822
13823 void
13824 PonderNextMoveEvent(newState)
13825      int newState;
13826 {
13827     if (newState == appData.ponderNextMove) return;
13828     if (gameMode == EditPosition) EditPositionDone(TRUE);
13829     if (newState) {
13830         SendToProgram("hard\n", &first);
13831         if (gameMode == TwoMachinesPlay) {
13832             SendToProgram("hard\n", &second);
13833         }
13834     } else {
13835         SendToProgram("easy\n", &first);
13836         thinkOutput[0] = NULLCHAR;
13837         if (gameMode == TwoMachinesPlay) {
13838             SendToProgram("easy\n", &second);
13839         }
13840     }
13841     appData.ponderNextMove = newState;
13842 }
13843
13844 void
13845 NewSettingEvent(option, feature, command, value)
13846      char *command;
13847      int option, value, *feature;
13848 {
13849     char buf[MSG_SIZ];
13850
13851     if (gameMode == EditPosition) EditPositionDone(TRUE);
13852     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13853     if(feature == NULL || *feature) SendToProgram(buf, &first);
13854     if (gameMode == TwoMachinesPlay) {
13855         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13856     }
13857 }
13858
13859 void
13860 ShowThinkingEvent()
13861 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13862 {
13863     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13864     int newState = appData.showThinking
13865         // [HGM] thinking: other features now need thinking output as well
13866         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13867     
13868     if (oldState == newState) return;
13869     oldState = newState;
13870     if (gameMode == EditPosition) EditPositionDone(TRUE);
13871     if (oldState) {
13872         SendToProgram("post\n", &first);
13873         if (gameMode == TwoMachinesPlay) {
13874             SendToProgram("post\n", &second);
13875         }
13876     } else {
13877         SendToProgram("nopost\n", &first);
13878         thinkOutput[0] = NULLCHAR;
13879         if (gameMode == TwoMachinesPlay) {
13880             SendToProgram("nopost\n", &second);
13881         }
13882     }
13883 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13884 }
13885
13886 void
13887 AskQuestionEvent(title, question, replyPrefix, which)
13888      char *title; char *question; char *replyPrefix; char *which;
13889 {
13890   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13891   if (pr == NoProc) return;
13892   AskQuestion(title, question, replyPrefix, pr);
13893 }
13894
13895 void
13896 DisplayMove(moveNumber)
13897      int moveNumber;
13898 {
13899     char message[MSG_SIZ];
13900     char res[MSG_SIZ];
13901     char cpThinkOutput[MSG_SIZ];
13902
13903     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13904     
13905     if (moveNumber == forwardMostMove - 1 || 
13906         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13907
13908         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13909
13910         if (strchr(cpThinkOutput, '\n')) {
13911             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13912         }
13913     } else {
13914         *cpThinkOutput = NULLCHAR;
13915     }
13916
13917     /* [AS] Hide thinking from human user */
13918     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13919         *cpThinkOutput = NULLCHAR;
13920         if( thinkOutput[0] != NULLCHAR ) {
13921             int i;
13922
13923             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13924                 cpThinkOutput[i] = '.';
13925             }
13926             cpThinkOutput[i] = NULLCHAR;
13927             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13928         }
13929     }
13930
13931     if (moveNumber == forwardMostMove - 1 &&
13932         gameInfo.resultDetails != NULL) {
13933         if (gameInfo.resultDetails[0] == NULLCHAR) {
13934             sprintf(res, " %s", PGNResult(gameInfo.result));
13935         } else {
13936             sprintf(res, " {%s} %s",
13937                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13938         }
13939     } else {
13940         res[0] = NULLCHAR;
13941     }
13942
13943     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13944         DisplayMessage(res, cpThinkOutput);
13945     } else {
13946         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13947                 WhiteOnMove(moveNumber) ? " " : ".. ",
13948                 parseList[moveNumber], res);
13949         DisplayMessage(message, cpThinkOutput);
13950     }
13951 }
13952
13953 void
13954 DisplayComment(moveNumber, text)
13955      int moveNumber;
13956      char *text;
13957 {
13958     char title[MSG_SIZ];
13959     char buf[8000]; // comment can be long!
13960     int score, depth;
13961     
13962     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13963       strcpy(title, "Comment");
13964     } else {
13965       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13966               WhiteOnMove(moveNumber) ? " " : ".. ",
13967               parseList[moveNumber]);
13968     }
13969     // [HGM] PV info: display PV info together with (or as) comment
13970     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13971       if(text == NULL) text = "";                                           
13972       score = pvInfoList[moveNumber].score;
13973       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13974               depth, (pvInfoList[moveNumber].time+50)/100, text);
13975       text = buf;
13976     }
13977     if (text != NULL && (appData.autoDisplayComment || commentUp))
13978         CommentPopUp(title, text);
13979 }
13980
13981 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13982  * might be busy thinking or pondering.  It can be omitted if your
13983  * gnuchess is configured to stop thinking immediately on any user
13984  * input.  However, that gnuchess feature depends on the FIONREAD
13985  * ioctl, which does not work properly on some flavors of Unix.
13986  */
13987 void
13988 Attention(cps)
13989      ChessProgramState *cps;
13990 {
13991 #if ATTENTION
13992     if (!cps->useSigint) return;
13993     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13994     switch (gameMode) {
13995       case MachinePlaysWhite:
13996       case MachinePlaysBlack:
13997       case TwoMachinesPlay:
13998       case IcsPlayingWhite:
13999       case IcsPlayingBlack:
14000       case AnalyzeMode:
14001       case AnalyzeFile:
14002         /* Skip if we know it isn't thinking */
14003         if (!cps->maybeThinking) return;
14004         if (appData.debugMode)
14005           fprintf(debugFP, "Interrupting %s\n", cps->which);
14006         InterruptChildProcess(cps->pr);
14007         cps->maybeThinking = FALSE;
14008         break;
14009       default:
14010         break;
14011     }
14012 #endif /*ATTENTION*/
14013 }
14014
14015 int
14016 CheckFlags()
14017 {
14018     if (whiteTimeRemaining <= 0) {
14019         if (!whiteFlag) {
14020             whiteFlag = TRUE;
14021             if (appData.icsActive) {
14022                 if (appData.autoCallFlag &&
14023                     gameMode == IcsPlayingBlack && !blackFlag) {
14024                   SendToICS(ics_prefix);
14025                   SendToICS("flag\n");
14026                 }
14027             } else {
14028                 if (blackFlag) {
14029                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14030                 } else {
14031                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14032                     if (appData.autoCallFlag) {
14033                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14034                         return TRUE;
14035                     }
14036                 }
14037             }
14038         }
14039     }
14040     if (blackTimeRemaining <= 0) {
14041         if (!blackFlag) {
14042             blackFlag = TRUE;
14043             if (appData.icsActive) {
14044                 if (appData.autoCallFlag &&
14045                     gameMode == IcsPlayingWhite && !whiteFlag) {
14046                   SendToICS(ics_prefix);
14047                   SendToICS("flag\n");
14048                 }
14049             } else {
14050                 if (whiteFlag) {
14051                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14052                 } else {
14053                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14054                     if (appData.autoCallFlag) {
14055                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14056                         return TRUE;
14057                     }
14058                 }
14059             }
14060         }
14061     }
14062     return FALSE;
14063 }
14064
14065 void
14066 CheckTimeControl()
14067 {
14068     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14069         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14070
14071     /*
14072      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14073      */
14074     if ( !WhiteOnMove(forwardMostMove) ) {
14075         /* White made time control */
14076         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14077         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14078         /* [HGM] time odds: correct new time quota for time odds! */
14079                                             / WhitePlayer()->timeOdds;
14080         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14081     } else {
14082         lastBlack -= blackTimeRemaining;
14083         /* Black made time control */
14084         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14085                                             / WhitePlayer()->other->timeOdds;
14086         lastWhite = whiteTimeRemaining;
14087     }
14088 }
14089
14090 void
14091 DisplayBothClocks()
14092 {
14093     int wom = gameMode == EditPosition ?
14094       !blackPlaysFirst : WhiteOnMove(currentMove);
14095     DisplayWhiteClock(whiteTimeRemaining, wom);
14096     DisplayBlackClock(blackTimeRemaining, !wom);
14097 }
14098
14099
14100 /* Timekeeping seems to be a portability nightmare.  I think everyone
14101    has ftime(), but I'm really not sure, so I'm including some ifdefs
14102    to use other calls if you don't.  Clocks will be less accurate if
14103    you have neither ftime nor gettimeofday.
14104 */
14105
14106 /* VS 2008 requires the #include outside of the function */
14107 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14108 #include <sys/timeb.h>
14109 #endif
14110
14111 /* Get the current time as a TimeMark */
14112 void
14113 GetTimeMark(tm)
14114      TimeMark *tm;
14115 {
14116 #if HAVE_GETTIMEOFDAY
14117
14118     struct timeval timeVal;
14119     struct timezone timeZone;
14120
14121     gettimeofday(&timeVal, &timeZone);
14122     tm->sec = (long) timeVal.tv_sec; 
14123     tm->ms = (int) (timeVal.tv_usec / 1000L);
14124
14125 #else /*!HAVE_GETTIMEOFDAY*/
14126 #if HAVE_FTIME
14127
14128 // include <sys/timeb.h> / moved to just above start of function
14129     struct timeb timeB;
14130
14131     ftime(&timeB);
14132     tm->sec = (long) timeB.time;
14133     tm->ms = (int) timeB.millitm;
14134
14135 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14136     tm->sec = (long) time(NULL);
14137     tm->ms = 0;
14138 #endif
14139 #endif
14140 }
14141
14142 /* Return the difference in milliseconds between two
14143    time marks.  We assume the difference will fit in a long!
14144 */
14145 long
14146 SubtractTimeMarks(tm2, tm1)
14147      TimeMark *tm2, *tm1;
14148 {
14149     return 1000L*(tm2->sec - tm1->sec) +
14150            (long) (tm2->ms - tm1->ms);
14151 }
14152
14153
14154 /*
14155  * Code to manage the game clocks.
14156  *
14157  * In tournament play, black starts the clock and then white makes a move.
14158  * We give the human user a slight advantage if he is playing white---the
14159  * clocks don't run until he makes his first move, so it takes zero time.
14160  * Also, we don't account for network lag, so we could get out of sync
14161  * with GNU Chess's clock -- but then, referees are always right.  
14162  */
14163
14164 static TimeMark tickStartTM;
14165 static long intendedTickLength;
14166
14167 long
14168 NextTickLength(timeRemaining)
14169      long timeRemaining;
14170 {
14171     long nominalTickLength, nextTickLength;
14172
14173     if (timeRemaining > 0L && timeRemaining <= 10000L)
14174       nominalTickLength = 100L;
14175     else
14176       nominalTickLength = 1000L;
14177     nextTickLength = timeRemaining % nominalTickLength;
14178     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14179
14180     return nextTickLength;
14181 }
14182
14183 /* Adjust clock one minute up or down */
14184 void
14185 AdjustClock(Boolean which, int dir)
14186 {
14187     if(which) blackTimeRemaining += 60000*dir;
14188     else      whiteTimeRemaining += 60000*dir;
14189     DisplayBothClocks();
14190 }
14191
14192 /* Stop clocks and reset to a fresh time control */
14193 void
14194 ResetClocks() 
14195 {
14196     (void) StopClockTimer();
14197     if (appData.icsActive) {
14198         whiteTimeRemaining = blackTimeRemaining = 0;
14199     } else if (searchTime) {
14200         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14201         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14202     } else { /* [HGM] correct new time quote for time odds */
14203         whiteTC = blackTC = fullTimeControlString;
14204         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14205         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14206     }
14207     if (whiteFlag || blackFlag) {
14208         DisplayTitle("");
14209         whiteFlag = blackFlag = FALSE;
14210     }
14211     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14212     DisplayBothClocks();
14213 }
14214
14215 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14216
14217 /* Decrement running clock by amount of time that has passed */
14218 void
14219 DecrementClocks()
14220 {
14221     long timeRemaining;
14222     long lastTickLength, fudge;
14223     TimeMark now;
14224
14225     if (!appData.clockMode) return;
14226     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14227         
14228     GetTimeMark(&now);
14229
14230     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14231
14232     /* Fudge if we woke up a little too soon */
14233     fudge = intendedTickLength - lastTickLength;
14234     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14235
14236     if (WhiteOnMove(forwardMostMove)) {
14237         if(whiteNPS >= 0) lastTickLength = 0;
14238         timeRemaining = whiteTimeRemaining -= lastTickLength;
14239         if(timeRemaining < 0) {
14240             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14241             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14242                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14243                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14244             }
14245         }
14246         DisplayWhiteClock(whiteTimeRemaining - fudge,
14247                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14248     } else {
14249         if(blackNPS >= 0) lastTickLength = 0;
14250         timeRemaining = blackTimeRemaining -= lastTickLength;
14251         if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
14252             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14253             if(suddenDeath) {
14254                 blackStartMove = forwardMostMove;
14255                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14256             }
14257         }
14258         DisplayBlackClock(blackTimeRemaining - fudge,
14259                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14260     }
14261     if (CheckFlags()) return;
14262         
14263     tickStartTM = now;
14264     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14265     StartClockTimer(intendedTickLength);
14266
14267     /* if the time remaining has fallen below the alarm threshold, sound the
14268      * alarm. if the alarm has sounded and (due to a takeback or time control
14269      * with increment) the time remaining has increased to a level above the
14270      * threshold, reset the alarm so it can sound again. 
14271      */
14272     
14273     if (appData.icsActive && appData.icsAlarm) {
14274
14275         /* make sure we are dealing with the user's clock */
14276         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14277                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14278            )) return;
14279
14280         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14281             alarmSounded = FALSE;
14282         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14283             PlayAlarmSound();
14284             alarmSounded = TRUE;
14285         }
14286     }
14287 }
14288
14289
14290 /* A player has just moved, so stop the previously running
14291    clock and (if in clock mode) start the other one.
14292    We redisplay both clocks in case we're in ICS mode, because
14293    ICS gives us an update to both clocks after every move.
14294    Note that this routine is called *after* forwardMostMove
14295    is updated, so the last fractional tick must be subtracted
14296    from the color that is *not* on move now.
14297 */
14298 void
14299 SwitchClocks(int newMoveNr)
14300 {
14301     long lastTickLength;
14302     TimeMark now;
14303     int flagged = FALSE;
14304
14305     GetTimeMark(&now);
14306
14307     if (StopClockTimer() && appData.clockMode) {
14308         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14309         if (!WhiteOnMove(forwardMostMove)) {
14310             if(blackNPS >= 0) lastTickLength = 0;
14311             blackTimeRemaining -= lastTickLength;
14312            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14313 //         if(pvInfoList[forwardMostMove-1].time == -1)
14314                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14315                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14316         } else {
14317            if(whiteNPS >= 0) lastTickLength = 0;
14318            whiteTimeRemaining -= lastTickLength;
14319            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14320 //         if(pvInfoList[forwardMostMove-1].time == -1)
14321                  pvInfoList[forwardMostMove-1].time = 
14322                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14323         }
14324         flagged = CheckFlags();
14325     }
14326     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14327     CheckTimeControl();
14328
14329     if (flagged || !appData.clockMode) return;
14330
14331     switch (gameMode) {
14332       case MachinePlaysBlack:
14333       case MachinePlaysWhite:
14334       case BeginningOfGame:
14335         if (pausing) return;
14336         break;
14337
14338       case EditGame:
14339       case PlayFromGameFile:
14340       case IcsExamining:
14341         return;
14342
14343       default:
14344         break;
14345     }
14346
14347     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14348         if(WhiteOnMove(forwardMostMove))
14349              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14350         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14351     }
14352
14353     tickStartTM = now;
14354     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14355       whiteTimeRemaining : blackTimeRemaining);
14356     StartClockTimer(intendedTickLength);
14357 }
14358         
14359
14360 /* Stop both clocks */
14361 void
14362 StopClocks()
14363 {       
14364     long lastTickLength;
14365     TimeMark now;
14366
14367     if (!StopClockTimer()) return;
14368     if (!appData.clockMode) return;
14369
14370     GetTimeMark(&now);
14371
14372     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14373     if (WhiteOnMove(forwardMostMove)) {
14374         if(whiteNPS >= 0) lastTickLength = 0;
14375         whiteTimeRemaining -= lastTickLength;
14376         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14377     } else {
14378         if(blackNPS >= 0) lastTickLength = 0;
14379         blackTimeRemaining -= lastTickLength;
14380         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14381     }
14382     CheckFlags();
14383 }
14384         
14385 /* Start clock of player on move.  Time may have been reset, so
14386    if clock is already running, stop and restart it. */
14387 void
14388 StartClocks()
14389 {
14390     (void) StopClockTimer(); /* in case it was running already */
14391     DisplayBothClocks();
14392     if (CheckFlags()) return;
14393
14394     if (!appData.clockMode) return;
14395     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14396
14397     GetTimeMark(&tickStartTM);
14398     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14399       whiteTimeRemaining : blackTimeRemaining);
14400
14401    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14402     whiteNPS = blackNPS = -1; 
14403     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14404        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14405         whiteNPS = first.nps;
14406     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14407        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14408         blackNPS = first.nps;
14409     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14410         whiteNPS = second.nps;
14411     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14412         blackNPS = second.nps;
14413     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14414
14415     StartClockTimer(intendedTickLength);
14416 }
14417
14418 char *
14419 TimeString(ms)
14420      long ms;
14421 {
14422     long second, minute, hour, day;
14423     char *sign = "";
14424     static char buf[32];
14425     
14426     if (ms > 0 && ms <= 9900) {
14427       /* convert milliseconds to tenths, rounding up */
14428       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14429
14430       sprintf(buf, " %03.1f ", tenths/10.0);
14431       return buf;
14432     }
14433
14434     /* convert milliseconds to seconds, rounding up */
14435     /* use floating point to avoid strangeness of integer division
14436        with negative dividends on many machines */
14437     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14438
14439     if (second < 0) {
14440         sign = "-";
14441         second = -second;
14442     }
14443     
14444     day = second / (60 * 60 * 24);
14445     second = second % (60 * 60 * 24);
14446     hour = second / (60 * 60);
14447     second = second % (60 * 60);
14448     minute = second / 60;
14449     second = second % 60;
14450     
14451     if (day > 0)
14452       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14453               sign, day, hour, minute, second);
14454     else if (hour > 0)
14455       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14456     else
14457       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14458     
14459     return buf;
14460 }
14461
14462
14463 /*
14464  * This is necessary because some C libraries aren't ANSI C compliant yet.
14465  */
14466 char *
14467 StrStr(string, match)
14468      char *string, *match;
14469 {
14470     int i, length;
14471     
14472     length = strlen(match);
14473     
14474     for (i = strlen(string) - length; i >= 0; i--, string++)
14475       if (!strncmp(match, string, length))
14476         return string;
14477     
14478     return NULL;
14479 }
14480
14481 char *
14482 StrCaseStr(string, match)
14483      char *string, *match;
14484 {
14485     int i, j, length;
14486     
14487     length = strlen(match);
14488     
14489     for (i = strlen(string) - length; i >= 0; i--, string++) {
14490         for (j = 0; j < length; j++) {
14491             if (ToLower(match[j]) != ToLower(string[j]))
14492               break;
14493         }
14494         if (j == length) return string;
14495     }
14496
14497     return NULL;
14498 }
14499
14500 #ifndef _amigados
14501 int
14502 StrCaseCmp(s1, s2)
14503      char *s1, *s2;
14504 {
14505     char c1, c2;
14506     
14507     for (;;) {
14508         c1 = ToLower(*s1++);
14509         c2 = ToLower(*s2++);
14510         if (c1 > c2) return 1;
14511         if (c1 < c2) return -1;
14512         if (c1 == NULLCHAR) return 0;
14513     }
14514 }
14515
14516
14517 int
14518 ToLower(c)
14519      int c;
14520 {
14521     return isupper(c) ? tolower(c) : c;
14522 }
14523
14524
14525 int
14526 ToUpper(c)
14527      int c;
14528 {
14529     return islower(c) ? toupper(c) : c;
14530 }
14531 #endif /* !_amigados    */
14532
14533 char *
14534 StrSave(s)
14535      char *s;
14536 {
14537     char *ret;
14538
14539     if ((ret = (char *) malloc(strlen(s) + 1))) {
14540         strcpy(ret, s);
14541     }
14542     return ret;
14543 }
14544
14545 char *
14546 StrSavePtr(s, savePtr)
14547      char *s, **savePtr;
14548 {
14549     if (*savePtr) {
14550         free(*savePtr);
14551     }
14552     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14553         strcpy(*savePtr, s);
14554     }
14555     return(*savePtr);
14556 }
14557
14558 char *
14559 PGNDate()
14560 {
14561     time_t clock;
14562     struct tm *tm;
14563     char buf[MSG_SIZ];
14564
14565     clock = time((time_t *)NULL);
14566     tm = localtime(&clock);
14567     sprintf(buf, "%04d.%02d.%02d",
14568             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14569     return StrSave(buf);
14570 }
14571
14572
14573 char *
14574 PositionToFEN(move, overrideCastling)
14575      int move;
14576      char *overrideCastling;
14577 {
14578     int i, j, fromX, fromY, toX, toY;
14579     int whiteToPlay;
14580     char buf[128];
14581     char *p, *q;
14582     int emptycount;
14583     ChessSquare piece;
14584
14585     whiteToPlay = (gameMode == EditPosition) ?
14586       !blackPlaysFirst : (move % 2 == 0);
14587     p = buf;
14588
14589     /* Piece placement data */
14590     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14591         emptycount = 0;
14592         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14593             if (boards[move][i][j] == EmptySquare) {
14594                 emptycount++;
14595             } else { ChessSquare piece = boards[move][i][j];
14596                 if (emptycount > 0) {
14597                     if(emptycount<10) /* [HGM] can be >= 10 */
14598                         *p++ = '0' + emptycount;
14599                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14600                     emptycount = 0;
14601                 }
14602                 if(PieceToChar(piece) == '+') {
14603                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14604                     *p++ = '+';
14605                     piece = (ChessSquare)(DEMOTED piece);
14606                 } 
14607                 *p++ = PieceToChar(piece);
14608                 if(p[-1] == '~') {
14609                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14610                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14611                     *p++ = '~';
14612                 }
14613             }
14614         }
14615         if (emptycount > 0) {
14616             if(emptycount<10) /* [HGM] can be >= 10 */
14617                 *p++ = '0' + emptycount;
14618             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14619             emptycount = 0;
14620         }
14621         *p++ = '/';
14622     }
14623     *(p - 1) = ' ';
14624
14625     /* [HGM] print Crazyhouse or Shogi holdings */
14626     if( gameInfo.holdingsWidth ) {
14627         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14628         q = p;
14629         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14630             piece = boards[move][i][BOARD_WIDTH-1];
14631             if( piece != EmptySquare )
14632               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14633                   *p++ = PieceToChar(piece);
14634         }
14635         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14636             piece = boards[move][BOARD_HEIGHT-i-1][0];
14637             if( piece != EmptySquare )
14638               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14639                   *p++ = PieceToChar(piece);
14640         }
14641
14642         if( q == p ) *p++ = '-';
14643         *p++ = ']';
14644         *p++ = ' ';
14645     }
14646
14647     /* Active color */
14648     *p++ = whiteToPlay ? 'w' : 'b';
14649     *p++ = ' ';
14650
14651   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14652     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14653   } else {
14654   if(nrCastlingRights) {
14655      q = p;
14656      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14657        /* [HGM] write directly from rights */
14658            if(boards[move][CASTLING][2] != NoRights &&
14659               boards[move][CASTLING][0] != NoRights   )
14660                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14661            if(boards[move][CASTLING][2] != NoRights &&
14662               boards[move][CASTLING][1] != NoRights   )
14663                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14664            if(boards[move][CASTLING][5] != NoRights &&
14665               boards[move][CASTLING][3] != NoRights   )
14666                 *p++ = boards[move][CASTLING][3] + AAA;
14667            if(boards[move][CASTLING][5] != NoRights &&
14668               boards[move][CASTLING][4] != NoRights   )
14669                 *p++ = boards[move][CASTLING][4] + AAA;
14670      } else {
14671
14672         /* [HGM] write true castling rights */
14673         if( nrCastlingRights == 6 ) {
14674             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14675                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14676             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14677                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14678             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14679                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14680             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14681                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14682         }
14683      }
14684      if (q == p) *p++ = '-'; /* No castling rights */
14685      *p++ = ' ';
14686   }
14687
14688   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14689      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14690     /* En passant target square */
14691     if (move > backwardMostMove) {
14692         fromX = moveList[move - 1][0] - AAA;
14693         fromY = moveList[move - 1][1] - ONE;
14694         toX = moveList[move - 1][2] - AAA;
14695         toY = moveList[move - 1][3] - ONE;
14696         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14697             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14698             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14699             fromX == toX) {
14700             /* 2-square pawn move just happened */
14701             *p++ = toX + AAA;
14702             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14703         } else {
14704             *p++ = '-';
14705         }
14706     } else if(move == backwardMostMove) {
14707         // [HGM] perhaps we should always do it like this, and forget the above?
14708         if((signed char)boards[move][EP_STATUS] >= 0) {
14709             *p++ = boards[move][EP_STATUS] + AAA;
14710             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14711         } else {
14712             *p++ = '-';
14713         }
14714     } else {
14715         *p++ = '-';
14716     }
14717     *p++ = ' ';
14718   }
14719   }
14720
14721     /* [HGM] find reversible plies */
14722     {   int i = 0, j=move;
14723
14724         if (appData.debugMode) { int k;
14725             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14726             for(k=backwardMostMove; k<=forwardMostMove; k++)
14727                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14728
14729         }
14730
14731         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14732         if( j == backwardMostMove ) i += initialRulePlies;
14733         sprintf(p, "%d ", i);
14734         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14735     }
14736     /* Fullmove number */
14737     sprintf(p, "%d", (move / 2) + 1);
14738     
14739     return StrSave(buf);
14740 }
14741
14742 Boolean
14743 ParseFEN(board, blackPlaysFirst, fen)
14744     Board board;
14745      int *blackPlaysFirst;
14746      char *fen;
14747 {
14748     int i, j;
14749     char *p, c;
14750     int emptycount;
14751     ChessSquare piece;
14752
14753     p = fen;
14754
14755     /* [HGM] by default clear Crazyhouse holdings, if present */
14756     if(gameInfo.holdingsWidth) {
14757        for(i=0; i<BOARD_HEIGHT; i++) {
14758            board[i][0]             = EmptySquare; /* black holdings */
14759            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14760            board[i][1]             = (ChessSquare) 0; /* black counts */
14761            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14762        }
14763     }
14764
14765     /* Piece placement data */
14766     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14767         j = 0;
14768         for (;;) {
14769             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14770                 if (*p == '/') p++;
14771                 emptycount = gameInfo.boardWidth - j;
14772                 while (emptycount--)
14773                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14774                 break;
14775 #if(BOARD_FILES >= 10)
14776             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14777                 p++; emptycount=10;
14778                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14779                 while (emptycount--)
14780                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14781 #endif
14782             } else if (isdigit(*p)) {
14783                 emptycount = *p++ - '0';
14784                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14785                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14786                 while (emptycount--)
14787                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14788             } else if (*p == '+' || isalpha(*p)) {
14789                 if (j >= gameInfo.boardWidth) return FALSE;
14790                 if(*p=='+') {
14791                     piece = CharToPiece(*++p);
14792                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14793                     piece = (ChessSquare) (PROMOTED piece ); p++;
14794                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14795                 } else piece = CharToPiece(*p++);
14796
14797                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14798                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14799                     piece = (ChessSquare) (PROMOTED piece);
14800                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14801                     p++;
14802                 }
14803                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14804             } else {
14805                 return FALSE;
14806             }
14807         }
14808     }
14809     while (*p == '/' || *p == ' ') p++;
14810
14811     /* [HGM] look for Crazyhouse holdings here */
14812     while(*p==' ') p++;
14813     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14814         if(*p == '[') p++;
14815         if(*p == '-' ) *p++; /* empty holdings */ else {
14816             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14817             /* if we would allow FEN reading to set board size, we would   */
14818             /* have to add holdings and shift the board read so far here   */
14819             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14820                 *p++;
14821                 if((int) piece >= (int) BlackPawn ) {
14822                     i = (int)piece - (int)BlackPawn;
14823                     i = PieceToNumber((ChessSquare)i);
14824                     if( i >= gameInfo.holdingsSize ) return FALSE;
14825                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14826                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14827                 } else {
14828                     i = (int)piece - (int)WhitePawn;
14829                     i = PieceToNumber((ChessSquare)i);
14830                     if( i >= gameInfo.holdingsSize ) return FALSE;
14831                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14832                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14833                 }
14834             }
14835         }
14836         if(*p == ']') *p++;
14837     }
14838
14839     while(*p == ' ') p++;
14840
14841     /* Active color */
14842     c = *p++;
14843     if(appData.colorNickNames) {
14844       if( c == appData.colorNickNames[0] ) c = 'w'; else
14845       if( c == appData.colorNickNames[1] ) c = 'b';
14846     }
14847     switch (c) {
14848       case 'w':
14849         *blackPlaysFirst = FALSE;
14850         break;
14851       case 'b': 
14852         *blackPlaysFirst = TRUE;
14853         break;
14854       default:
14855         return FALSE;
14856     }
14857
14858     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14859     /* return the extra info in global variiables             */
14860
14861     /* set defaults in case FEN is incomplete */
14862     board[EP_STATUS] = EP_UNKNOWN;
14863     for(i=0; i<nrCastlingRights; i++ ) {
14864         board[CASTLING][i] =
14865             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14866     }   /* assume possible unless obviously impossible */
14867     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14868     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14869     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14870                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14871     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14872     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14873     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14874                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14875     FENrulePlies = 0;
14876
14877     while(*p==' ') p++;
14878     if(nrCastlingRights) {
14879       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14880           /* castling indicator present, so default becomes no castlings */
14881           for(i=0; i<nrCastlingRights; i++ ) {
14882                  board[CASTLING][i] = NoRights;
14883           }
14884       }
14885       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14886              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14887              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14888              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14889         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14890
14891         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14892             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14893             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14894         }
14895         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14896             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14897         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14898                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14899         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14900                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14901         switch(c) {
14902           case'K':
14903               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14904               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14905               board[CASTLING][2] = whiteKingFile;
14906               break;
14907           case'Q':
14908               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14909               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14910               board[CASTLING][2] = whiteKingFile;
14911               break;
14912           case'k':
14913               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14914               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14915               board[CASTLING][5] = blackKingFile;
14916               break;
14917           case'q':
14918               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14919               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14920               board[CASTLING][5] = blackKingFile;
14921           case '-':
14922               break;
14923           default: /* FRC castlings */
14924               if(c >= 'a') { /* black rights */
14925                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14926                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14927                   if(i == BOARD_RGHT) break;
14928                   board[CASTLING][5] = i;
14929                   c -= AAA;
14930                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14931                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14932                   if(c > i)
14933                       board[CASTLING][3] = c;
14934                   else
14935                       board[CASTLING][4] = c;
14936               } else { /* white rights */
14937                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14938                     if(board[0][i] == WhiteKing) break;
14939                   if(i == BOARD_RGHT) break;
14940                   board[CASTLING][2] = i;
14941                   c -= AAA - 'a' + 'A';
14942                   if(board[0][c] >= WhiteKing) break;
14943                   if(c > i)
14944                       board[CASTLING][0] = c;
14945                   else
14946                       board[CASTLING][1] = c;
14947               }
14948         }
14949       }
14950       for(i=0; i<nrCastlingRights; i++)
14951         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14952     if (appData.debugMode) {
14953         fprintf(debugFP, "FEN castling rights:");
14954         for(i=0; i<nrCastlingRights; i++)
14955         fprintf(debugFP, " %d", board[CASTLING][i]);
14956         fprintf(debugFP, "\n");
14957     }
14958
14959       while(*p==' ') p++;
14960     }
14961
14962     /* read e.p. field in games that know e.p. capture */
14963     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14964        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14965       if(*p=='-') {
14966         p++; board[EP_STATUS] = EP_NONE;
14967       } else {
14968          char c = *p++ - AAA;
14969
14970          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14971          if(*p >= '0' && *p <='9') *p++;
14972          board[EP_STATUS] = c;
14973       }
14974     }
14975
14976
14977     if(sscanf(p, "%d", &i) == 1) {
14978         FENrulePlies = i; /* 50-move ply counter */
14979         /* (The move number is still ignored)    */
14980     }
14981
14982     return TRUE;
14983 }
14984       
14985 void
14986 EditPositionPasteFEN(char *fen)
14987 {
14988   if (fen != NULL) {
14989     Board initial_position;
14990
14991     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14992       DisplayError(_("Bad FEN position in clipboard"), 0);
14993       return ;
14994     } else {
14995       int savedBlackPlaysFirst = blackPlaysFirst;
14996       EditPositionEvent();
14997       blackPlaysFirst = savedBlackPlaysFirst;
14998       CopyBoard(boards[0], initial_position);
14999       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15000       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15001       DisplayBothClocks();
15002       DrawPosition(FALSE, boards[currentMove]);
15003     }
15004   }
15005 }
15006
15007 static char cseq[12] = "\\   ";
15008
15009 Boolean set_cont_sequence(char *new_seq)
15010 {
15011     int len;
15012     Boolean ret;
15013
15014     // handle bad attempts to set the sequence
15015         if (!new_seq)
15016                 return 0; // acceptable error - no debug
15017
15018     len = strlen(new_seq);
15019     ret = (len > 0) && (len < sizeof(cseq));
15020     if (ret)
15021         strcpy(cseq, new_seq);
15022     else if (appData.debugMode)
15023         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15024     return ret;
15025 }
15026
15027 /*
15028     reformat a source message so words don't cross the width boundary.  internal
15029     newlines are not removed.  returns the wrapped size (no null character unless
15030     included in source message).  If dest is NULL, only calculate the size required
15031     for the dest buffer.  lp argument indicats line position upon entry, and it's
15032     passed back upon exit.
15033 */
15034 int wrap(char *dest, char *src, int count, int width, int *lp)
15035 {
15036     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15037
15038     cseq_len = strlen(cseq);
15039     old_line = line = *lp;
15040     ansi = len = clen = 0;
15041
15042     for (i=0; i < count; i++)
15043     {
15044         if (src[i] == '\033')
15045             ansi = 1;
15046
15047         // if we hit the width, back up
15048         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15049         {
15050             // store i & len in case the word is too long
15051             old_i = i, old_len = len;
15052
15053             // find the end of the last word
15054             while (i && src[i] != ' ' && src[i] != '\n')
15055             {
15056                 i--;
15057                 len--;
15058             }
15059
15060             // word too long?  restore i & len before splitting it
15061             if ((old_i-i+clen) >= width)
15062             {
15063                 i = old_i;
15064                 len = old_len;
15065             }
15066
15067             // extra space?
15068             if (i && src[i-1] == ' ')
15069                 len--;
15070
15071             if (src[i] != ' ' && src[i] != '\n')
15072             {
15073                 i--;
15074                 if (len)
15075                     len--;
15076             }
15077
15078             // now append the newline and continuation sequence
15079             if (dest)
15080                 dest[len] = '\n';
15081             len++;
15082             if (dest)
15083                 strncpy(dest+len, cseq, cseq_len);
15084             len += cseq_len;
15085             line = cseq_len;
15086             clen = cseq_len;
15087             continue;
15088         }
15089
15090         if (dest)
15091             dest[len] = src[i];
15092         len++;
15093         if (!ansi)
15094             line++;
15095         if (src[i] == '\n')
15096             line = 0;
15097         if (src[i] == 'm')
15098             ansi = 0;
15099     }
15100     if (dest && appData.debugMode)
15101     {
15102         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15103             count, width, line, len, *lp);
15104         show_bytes(debugFP, src, count);
15105         fprintf(debugFP, "\ndest: ");
15106         show_bytes(debugFP, dest, len);
15107         fprintf(debugFP, "\n");
15108     }
15109     *lp = dest ? line : old_line;
15110
15111     return len;
15112 }
15113
15114 // [HGM] vari: routines for shelving variations
15115
15116 void 
15117 PushTail(int firstMove, int lastMove)
15118 {
15119         int i, j, nrMoves = lastMove - firstMove;
15120
15121         if(appData.icsActive) { // only in local mode
15122                 forwardMostMove = currentMove; // mimic old ICS behavior
15123                 return;
15124         }
15125         if(storedGames >= MAX_VARIATIONS-1) return;
15126
15127         // push current tail of game on stack
15128         savedResult[storedGames] = gameInfo.result;
15129         savedDetails[storedGames] = gameInfo.resultDetails;
15130         gameInfo.resultDetails = NULL;
15131         savedFirst[storedGames] = firstMove;
15132         savedLast [storedGames] = lastMove;
15133         savedFramePtr[storedGames] = framePtr;
15134         framePtr -= nrMoves; // reserve space for the boards
15135         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15136             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15137             for(j=0; j<MOVE_LEN; j++)
15138                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15139             for(j=0; j<2*MOVE_LEN; j++)
15140                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15141             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15142             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15143             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15144             pvInfoList[firstMove+i-1].depth = 0;
15145             commentList[framePtr+i] = commentList[firstMove+i];
15146             commentList[firstMove+i] = NULL;
15147         }
15148
15149         storedGames++;
15150         forwardMostMove = firstMove; // truncate game so we can start variation
15151         if(storedGames == 1) GreyRevert(FALSE);
15152 }
15153
15154 Boolean
15155 PopTail(Boolean annotate)
15156 {
15157         int i, j, nrMoves;
15158         char buf[8000], moveBuf[20];
15159
15160         if(appData.icsActive) return FALSE; // only in local mode
15161         if(!storedGames) return FALSE; // sanity
15162         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15163
15164         storedGames--;
15165         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15166         nrMoves = savedLast[storedGames] - currentMove;
15167         if(annotate) {
15168                 int cnt = 10;
15169                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15170                 else strcpy(buf, "(");
15171                 for(i=currentMove; i<forwardMostMove; i++) {
15172                         if(WhiteOnMove(i))
15173                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15174                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15175                         strcat(buf, moveBuf);
15176                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15177                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15178                 }
15179                 strcat(buf, ")");
15180         }
15181         for(i=1; i<=nrMoves; i++) { // copy last variation back
15182             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15183             for(j=0; j<MOVE_LEN; j++)
15184                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15185             for(j=0; j<2*MOVE_LEN; j++)
15186                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15187             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15188             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15189             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15190             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15191             commentList[currentMove+i] = commentList[framePtr+i];
15192             commentList[framePtr+i] = NULL;
15193         }
15194         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15195         framePtr = savedFramePtr[storedGames];
15196         gameInfo.result = savedResult[storedGames];
15197         if(gameInfo.resultDetails != NULL) {
15198             free(gameInfo.resultDetails);
15199       }
15200         gameInfo.resultDetails = savedDetails[storedGames];
15201         forwardMostMove = currentMove + nrMoves;
15202         if(storedGames == 0) GreyRevert(TRUE);
15203         return TRUE;
15204 }
15205
15206 void 
15207 CleanupTail()
15208 {       // remove all shelved variations
15209         int i;
15210         for(i=0; i<storedGames; i++) {
15211             if(savedDetails[i])
15212                 free(savedDetails[i]);
15213             savedDetails[i] = NULL;
15214         }
15215         for(i=framePtr; i<MAX_MOVES; i++) {
15216                 if(commentList[i]) free(commentList[i]);
15217                 commentList[i] = NULL;
15218         }
15219         framePtr = MAX_MOVES-1;
15220         storedGames = 0;
15221 }
15222
15223 void
15224 LoadVariation(int index, char *text)
15225 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15226         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15227         int level = 0, move;
15228
15229         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15230         // first find outermost bracketing variation
15231         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15232             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15233                 if(*p == '{') wait = '}'; else
15234                 if(*p == '[') wait = ']'; else
15235                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15236                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15237             }
15238             if(*p == wait) wait = NULLCHAR; // closing ]} found
15239             p++;
15240         }
15241         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15242         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15243         end[1] = NULLCHAR; // clip off comment beyond variation
15244         ToNrEvent(currentMove-1);
15245         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15246         // kludge: use ParsePV() to append variation to game
15247         move = currentMove;
15248         ParsePV(start, TRUE);
15249         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15250         ClearPremoveHighlights();
15251         CommentPopDown();
15252         ToNrEvent(currentMove+1);
15253 }
15254