Extend mate test to drop games
[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,
162                       int toX, int toY));
163 void HandleMachineMove P((char *message, ChessProgramState *cps));
164 int AutoPlayOneMove P((void));
165 int LoadGameOneMove P((ChessMove readAhead));
166 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
167 int LoadPositionFromFile P((char *filename, int n, char *title));
168 int SavePositionToFile P((char *filename));
169 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170                                                                                 Board board));
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174                    /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186                            char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188                         int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 void ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
195
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
206 int RegisterMove P((void));
207 void MakeRegisteredMove P((void));
208 void TruncateGame P((void));
209 int looking_at P((char *, int *, char *));
210 void CopyPlayerNameIntoFileName P((char **, char *));
211 char *SavePart P((char *));
212 int SaveGameOldStyle P((FILE *));
213 int SaveGamePGN P((FILE *));
214 void GetTimeMark P((TimeMark *));
215 long SubtractTimeMarks P((TimeMark *, TimeMark *));
216 int CheckFlags P((void));
217 long NextTickLength P((long));
218 void CheckTimeControl P((void));
219 void show_bytes P((FILE *, char *, int));
220 int string_to_rating P((char *str));
221 void ParseFeatures P((char* args, ChessProgramState *cps));
222 void InitBackEnd3 P((void));
223 void FeatureDone P((ChessProgramState* cps, int val));
224 void InitChessProgram P((ChessProgramState *cps, int setup));
225 void OutputKibitz(int window, char *text);
226 int PerpetualChase(int first, int last);
227 int EngineOutputIsUp();
228 void InitDrawingSizes(int x, int y);
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271
272 /* States for ics_getting_history */
273 #define H_FALSE 0
274 #define H_REQUESTED 1
275 #define H_GOT_REQ_HEADER 2
276 #define H_GOT_UNREQ_HEADER 3
277 #define H_GETTING_MOVES 4
278 #define H_GOT_UNWANTED_HEADER 5
279
280 /* whosays values for GameEnds */
281 #define GE_ICS 0
282 #define GE_ENGINE 1
283 #define GE_PLAYER 2
284 #define GE_FILE 3
285 #define GE_XBOARD 4
286 #define GE_ENGINE1 5
287 #define GE_ENGINE2 6
288
289 /* Maximum number of games in a cmail message */
290 #define CMAIL_MAX_GAMES 20
291
292 /* Different types of move when calling RegisterMove */
293 #define CMAIL_MOVE   0
294 #define CMAIL_RESIGN 1
295 #define CMAIL_DRAW   2
296 #define CMAIL_ACCEPT 3
297
298 /* Different types of result to remember for each game */
299 #define CMAIL_NOT_RESULT 0
300 #define CMAIL_OLD_RESULT 1
301 #define CMAIL_NEW_RESULT 2
302
303 /* Telnet protocol constants */
304 #define TN_WILL 0373
305 #define TN_WONT 0374
306 #define TN_DO   0375
307 #define TN_DONT 0376
308 #define TN_IAC  0377
309 #define TN_ECHO 0001
310 #define TN_SGA  0003
311 #define TN_PORT 23
312
313 /* [AS] */
314 static char * safeStrCpy( char * dst, const char * src, size_t count )
315 {
316     assert( dst != NULL );
317     assert( src != NULL );
318     assert( count > 0 );
319
320     strncpy( dst, src, count );
321     dst[ count-1 ] = '\0';
322     return dst;
323 }
324
325 /* Some compiler can't cast u64 to double
326  * This function do the job for us:
327
328  * We use the highest bit for cast, this only
329  * works if the highest bit is not
330  * in use (This should not happen)
331  *
332  * We used this for all compiler
333  */
334 double
335 u64ToDouble(u64 value)
336 {
337   double r;
338   u64 tmp = value & u64Const(0x7fffffffffffffff);
339   r = (double)(s64)tmp;
340   if (value & u64Const(0x8000000000000000))
341        r +=  9.2233720368547758080e18; /* 2^63 */
342  return r;
343 }
344
345 /* Fake up flags for now, as we aren't keeping track of castling
346    availability yet. [HGM] Change of logic: the flag now only
347    indicates the type of castlings allowed by the rule of the game.
348    The actual rights themselves are maintained in the array
349    castlingRights, as part of the game history, and are not probed
350    by this function.
351  */
352 int
353 PosFlags(index)
354 {
355   int flags = F_ALL_CASTLE_OK;
356   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
357   switch (gameInfo.variant) {
358   case VariantSuicide:
359     flags &= ~F_ALL_CASTLE_OK;
360   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
361     flags |= F_IGNORE_CHECK;
362   case VariantLosers:
363     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
364     break;
365   case VariantAtomic:
366     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
367     break;
368   case VariantKriegspiel:
369     flags |= F_KRIEGSPIEL_CAPTURE;
370     break;
371   case VariantCapaRandom: 
372   case VariantFischeRandom:
373     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
374   case VariantNoCastle:
375   case VariantShatranj:
376   case VariantCourier:
377   case VariantMakruk:
378     flags &= ~F_ALL_CASTLE_OK;
379     break;
380   default:
381     break;
382   }
383   return flags;
384 }
385
386 FILE *gameFileFP, *debugFP;
387
388 /* 
389     [AS] Note: sometimes, the sscanf() function is used to parse the input
390     into a fixed-size buffer. Because of this, we must be prepared to
391     receive strings as long as the size of the input buffer, which is currently
392     set to 4K for Windows and 8K for the rest.
393     So, we must either allocate sufficiently large buffers here, or
394     reduce the size of the input buffer in the input reading part.
395 */
396
397 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
398 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
399 char thinkOutput1[MSG_SIZ*10];
400
401 ChessProgramState first, second;
402
403 /* premove variables */
404 int premoveToX = 0;
405 int premoveToY = 0;
406 int premoveFromX = 0;
407 int premoveFromY = 0;
408 int premovePromoChar = 0;
409 int gotPremove = 0;
410 Boolean alarmSounded;
411 /* end premove variables */
412
413 char *ics_prefix = "$";
414 int ics_type = ICS_GENERIC;
415
416 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
417 int pauseExamForwardMostMove = 0;
418 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
419 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
420 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
421 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
422 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
423 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
424 int whiteFlag = FALSE, blackFlag = FALSE;
425 int userOfferedDraw = FALSE;
426 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
427 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
428 int cmailMoveType[CMAIL_MAX_GAMES];
429 long ics_clock_paused = 0;
430 ProcRef icsPR = NoProc, cmailPR = NoProc;
431 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
432 GameMode gameMode = BeginningOfGame;
433 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
434 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
435 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
436 int hiddenThinkOutputState = 0; /* [AS] */
437 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
438 int adjudicateLossPlies = 6;
439 char white_holding[64], black_holding[64];
440 TimeMark lastNodeCountTime;
441 long lastNodeCount=0;
442 int have_sent_ICS_logon = 0;
443 int movesPerSession;
444 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
445 long timeControl_2; /* [AS] Allow separate time controls */
446 char *fullTimeControlString = NULL; /* [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)
975 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
976     int result = -1; long temp, temp2;
977
978     if(**str != '+') return -1; // old params remain in force!
979     (*str)++;
980     if( NextTimeControlFromString( str, &temp ) ) return -1;
981
982     if(**str != '/') {
983         /* time only: incremental or sudden-death time control */
984         if(**str == '+') { /* increment follows; read it */
985             (*str)++;
986             if(result = NextIntegerFromString( str, &temp2)) return -1;
987             *inc = temp2 * 1000;
988         } else *inc = 0;
989         *moves = 0; *tc = temp * 1000; 
990         return 0;
991     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
992
993     (*str)++; /* classical time control */
994     result = NextTimeControlFromString( str, &temp2);
995     if(result == 0) {
996         *moves = temp/60;
997         *tc    = temp2 * 1000;
998         *inc   = 0;
999     }
1000     return result;
1001 }
1002
1003 int GetTimeQuota(int movenr)
1004 {   /* [HGM] get time to add from the multi-session time-control string */
1005     int moves=1; /* kludge to force reading of first session */
1006     long time, increment;
1007     char *s = fullTimeControlString;
1008
1009     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1010     do {
1011         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1012         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1013         if(movenr == -1) return time;    /* last move before new session     */
1014         if(!moves) return increment;     /* current session is incremental   */
1015         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1016     } while(movenr >= -1);               /* try again for next session       */
1017
1018     return 0; // no new time quota on this move
1019 }
1020
1021 int
1022 ParseTimeControl(tc, ti, mps)
1023      char *tc;
1024      int ti;
1025      int mps;
1026 {
1027   long tc1;
1028   long tc2;
1029   char buf[MSG_SIZ];
1030   
1031   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032   if(ti > 0) {
1033     if(mps)
1034       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1035     else sprintf(buf, "+%s+%d", tc, ti);
1036   } else {
1037     if(mps)
1038              sprintf(buf, "+%d/%s", mps, tc);
1039     else sprintf(buf, "+%s", tc);
1040   }
1041   fullTimeControlString = StrSave(buf);
1042   
1043   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1044     return FALSE;
1045   }
1046   
1047   if( *tc == '/' ) {
1048     /* Parse second time control */
1049     tc++;
1050     
1051     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1052       return FALSE;
1053     }
1054     
1055     if( tc2 == 0 ) {
1056       return FALSE;
1057     }
1058     
1059     timeControl_2 = tc2 * 1000;
1060   }
1061   else {
1062     timeControl_2 = 0;
1063   }
1064   
1065   if( tc1 == 0 ) {
1066     return FALSE;
1067   }
1068   
1069   timeControl = tc1 * 1000;
1070   
1071   if (ti >= 0) {
1072     timeIncrement = ti * 1000;  /* convert to ms */
1073     movesPerSession = 0;
1074   } else {
1075     timeIncrement = 0;
1076     movesPerSession = mps;
1077   }
1078   return TRUE;
1079 }
1080
1081 void
1082 InitBackEnd2()
1083 {
1084     if (appData.debugMode) {
1085         fprintf(debugFP, "%s\n", programVersion);
1086     }
1087
1088     set_cont_sequence(appData.wrapContSeq);
1089     if (appData.matchGames > 0) {
1090         appData.matchMode = TRUE;
1091     } else if (appData.matchMode) {
1092         appData.matchGames = 1;
1093     }
1094     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1095         appData.matchGames = appData.sameColorGames;
1096     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1097         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1098         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099     }
1100     Reset(TRUE, FALSE);
1101     if (appData.noChessProgram || first.protocolVersion == 1) {
1102       InitBackEnd3();
1103     } else {
1104       /* kludge: allow timeout for initial "feature" commands */
1105       FreezeUI();
1106       DisplayMessage("", _("Starting chess program"));
1107       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1108     }
1109 }
1110
1111 void
1112 InitBackEnd3 P((void))
1113 {
1114     GameMode initialMode;
1115     char buf[MSG_SIZ];
1116     int err;
1117
1118     InitChessProgram(&first, startedFromSetupPosition);
1119
1120     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1121         free(programVersion);
1122         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1123         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1124     }
1125
1126     if (appData.icsActive) {
1127 #ifdef WIN32
1128         /* [DM] Make a console window if needed [HGM] merged ifs */
1129         ConsoleCreate(); 
1130 #endif
1131         err = establish();
1132         if (err != 0) {
1133             if (*appData.icsCommPort != NULLCHAR) {
1134                 sprintf(buf, _("Could not open comm port %s"),  
1135                         appData.icsCommPort);
1136             } else {
1137                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1138                         appData.icsHost, appData.icsPort);
1139             }
1140             DisplayFatalError(buf, err, 1);
1141             return;
1142         }
1143         SetICSMode();
1144         telnetISR =
1145           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1146         fromUserISR =
1147           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1148         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1149             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1150     } else if (appData.noChessProgram) {
1151         SetNCPMode();
1152     } else {
1153         SetGNUMode();
1154     }
1155
1156     if (*appData.cmailGameName != NULLCHAR) {
1157         SetCmailMode();
1158         OpenLoopback(&cmailPR);
1159         cmailISR =
1160           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1161     }
1162     
1163     ThawUI();
1164     DisplayMessage("", "");
1165     if (StrCaseCmp(appData.initialMode, "") == 0) {
1166       initialMode = BeginningOfGame;
1167     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1168       initialMode = TwoMachinesPlay;
1169     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1170       initialMode = AnalyzeFile; 
1171     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1172       initialMode = AnalyzeMode;
1173     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1174       initialMode = MachinePlaysWhite;
1175     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1176       initialMode = MachinePlaysBlack;
1177     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1178       initialMode = EditGame;
1179     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1180       initialMode = EditPosition;
1181     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1182       initialMode = Training;
1183     } else {
1184       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1185       DisplayFatalError(buf, 0, 2);
1186       return;
1187     }
1188
1189     if (appData.matchMode) {
1190         /* Set up machine vs. machine match */
1191         if (appData.noChessProgram) {
1192             DisplayFatalError(_("Can't have a match with no chess programs"),
1193                               0, 2);
1194             return;
1195         }
1196         matchMode = TRUE;
1197         matchGame = 1;
1198         if (*appData.loadGameFile != NULLCHAR) {
1199             int index = appData.loadGameIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadGameFromFile(appData.loadGameFile,
1202                                   index,
1203                                   appData.loadGameFile, FALSE)) {
1204                 DisplayFatalError(_("Bad game file"), 0, 1);
1205                 return;
1206             }
1207         } else if (*appData.loadPositionFile != NULLCHAR) {
1208             int index = appData.loadPositionIndex; // [HGM] autoinc
1209             if(index<0) lastIndex = index = 1;
1210             if (!LoadPositionFromFile(appData.loadPositionFile,
1211                                       index,
1212                                       appData.loadPositionFile)) {
1213                 DisplayFatalError(_("Bad position file"), 0, 1);
1214                 return;
1215             }
1216         }
1217         TwoMachinesEvent();
1218     } else if (*appData.cmailGameName != NULLCHAR) {
1219         /* Set up cmail mode */
1220         ReloadCmailMsgEvent(TRUE);
1221     } else {
1222         /* Set up other modes */
1223         if (initialMode == AnalyzeFile) {
1224           if (*appData.loadGameFile == NULLCHAR) {
1225             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1226             return;
1227           }
1228         }
1229         if (*appData.loadGameFile != NULLCHAR) {
1230             (void) LoadGameFromFile(appData.loadGameFile,
1231                                     appData.loadGameIndex,
1232                                     appData.loadGameFile, TRUE);
1233         } else if (*appData.loadPositionFile != NULLCHAR) {
1234             (void) LoadPositionFromFile(appData.loadPositionFile,
1235                                         appData.loadPositionIndex,
1236                                         appData.loadPositionFile);
1237             /* [HGM] try to make self-starting even after FEN load */
1238             /* to allow automatic setup of fairy variants with wtm */
1239             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1240                 gameMode = BeginningOfGame;
1241                 setboardSpoiledMachineBlack = 1;
1242             }
1243             /* [HGM] loadPos: make that every new game uses the setup */
1244             /* from file as long as we do not switch variant          */
1245             if(!blackPlaysFirst) {
1246                 startedFromPositionFile = TRUE;
1247                 CopyBoard(filePosition, boards[0]);
1248             }
1249         }
1250         if (initialMode == AnalyzeMode) {
1251           if (appData.noChessProgram) {
1252             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1253             return;
1254           }
1255           if (appData.icsActive) {
1256             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1257             return;
1258           }
1259           AnalyzeModeEvent();
1260         } else if (initialMode == AnalyzeFile) {
1261           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1262           ShowThinkingEvent();
1263           AnalyzeFileEvent();
1264           AnalysisPeriodicEvent(1);
1265         } else if (initialMode == MachinePlaysWhite) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineWhiteEvent();
1277         } else if (initialMode == MachinePlaysBlack) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           MachineBlackEvent();
1289         } else if (initialMode == TwoMachinesPlay) {
1290           if (appData.noChessProgram) {
1291             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1292                               0, 2);
1293             return;
1294           }
1295           if (appData.icsActive) {
1296             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1297                               0, 2);
1298             return;
1299           }
1300           TwoMachinesEvent();
1301         } else if (initialMode == EditGame) {
1302           EditGameEvent();
1303         } else if (initialMode == EditPosition) {
1304           EditPositionEvent();
1305         } else if (initialMode == Training) {
1306           if (*appData.loadGameFile == NULLCHAR) {
1307             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308             return;
1309           }
1310           TrainingEvent();
1311         }
1312     }
1313 }
1314
1315 /*
1316  * Establish will establish a contact to a remote host.port.
1317  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1318  *  used to talk to the host.
1319  * Returns 0 if okay, error code if not.
1320  */
1321 int
1322 establish()
1323 {
1324     char buf[MSG_SIZ];
1325
1326     if (*appData.icsCommPort != NULLCHAR) {
1327         /* Talk to the host through a serial comm port */
1328         return OpenCommPort(appData.icsCommPort, &icsPR);
1329
1330     } else if (*appData.gateway != NULLCHAR) {
1331         if (*appData.remoteShell == NULLCHAR) {
1332             /* Use the rcmd protocol to run telnet program on a gateway host */
1333             snprintf(buf, sizeof(buf), "%s %s %s",
1334                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1335             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1336
1337         } else {
1338             /* Use the rsh program to run telnet program on a gateway host */
1339             if (*appData.remoteUser == NULLCHAR) {
1340                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1341                         appData.gateway, appData.telnetProgram,
1342                         appData.icsHost, appData.icsPort);
1343             } else {
1344                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1345                         appData.remoteShell, appData.gateway, 
1346                         appData.remoteUser, appData.telnetProgram,
1347                         appData.icsHost, appData.icsPort);
1348             }
1349             return StartChildProcess(buf, "", &icsPR);
1350
1351         }
1352     } else if (appData.useTelnet) {
1353         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1354
1355     } else {
1356         /* TCP socket interface differs somewhat between
1357            Unix and NT; handle details in the front end.
1358            */
1359         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1360     }
1361 }
1362
1363 void EscapeExpand(char *p, char *q)
1364 {       // [HGM] initstring: routine to shape up string arguments
1365         while(*p++ = *q++) if(p[-1] == '\\')
1366             switch(*q++) {
1367                 case 'n': p[-1] = '\n'; break;
1368                 case 'r': p[-1] = '\r'; break;
1369                 case 't': p[-1] = '\t'; break;
1370                 case '\\': p[-1] = '\\'; break;
1371                 case 0: *p = 0; return;
1372                 default: p[-1] = q[-1]; break;
1373             }
1374 }
1375
1376 void
1377 show_bytes(fp, buf, count)
1378      FILE *fp;
1379      char *buf;
1380      int count;
1381 {
1382     while (count--) {
1383         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1384             fprintf(fp, "\\%03o", *buf & 0xff);
1385         } else {
1386             putc(*buf, fp);
1387         }
1388         buf++;
1389     }
1390     fflush(fp);
1391 }
1392
1393 /* Returns an errno value */
1394 int
1395 OutputMaybeTelnet(pr, message, count, outError)
1396      ProcRef pr;
1397      char *message;
1398      int count;
1399      int *outError;
1400 {
1401     char buf[8192], *p, *q, *buflim;
1402     int left, newcount, outcount;
1403
1404     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1405         *appData.gateway != NULLCHAR) {
1406         if (appData.debugMode) {
1407             fprintf(debugFP, ">ICS: ");
1408             show_bytes(debugFP, message, count);
1409             fprintf(debugFP, "\n");
1410         }
1411         return OutputToProcess(pr, message, count, outError);
1412     }
1413
1414     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1415     p = message;
1416     q = buf;
1417     left = count;
1418     newcount = 0;
1419     while (left) {
1420         if (q >= buflim) {
1421             if (appData.debugMode) {
1422                 fprintf(debugFP, ">ICS: ");
1423                 show_bytes(debugFP, buf, newcount);
1424                 fprintf(debugFP, "\n");
1425             }
1426             outcount = OutputToProcess(pr, buf, newcount, outError);
1427             if (outcount < newcount) return -1; /* to be sure */
1428             q = buf;
1429             newcount = 0;
1430         }
1431         if (*p == '\n') {
1432             *q++ = '\r';
1433             newcount++;
1434         } else if (((unsigned char) *p) == TN_IAC) {
1435             *q++ = (char) TN_IAC;
1436             newcount ++;
1437         }
1438         *q++ = *p++;
1439         newcount++;
1440         left--;
1441     }
1442     if (appData.debugMode) {
1443         fprintf(debugFP, ">ICS: ");
1444         show_bytes(debugFP, buf, newcount);
1445         fprintf(debugFP, "\n");
1446     }
1447     outcount = OutputToProcess(pr, buf, newcount, outError);
1448     if (outcount < newcount) return -1; /* to be sure */
1449     return count;
1450 }
1451
1452 void
1453 read_from_player(isr, closure, message, count, error)
1454      InputSourceRef isr;
1455      VOIDSTAR closure;
1456      char *message;
1457      int count;
1458      int error;
1459 {
1460     int outError, outCount;
1461     static int gotEof = 0;
1462
1463     /* Pass data read from player on to ICS */
1464     if (count > 0) {
1465         gotEof = 0;
1466         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1467         if (outCount < count) {
1468             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469         }
1470     } else if (count < 0) {
1471         RemoveInputSource(isr);
1472         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1473     } else if (gotEof++ > 0) {
1474         RemoveInputSource(isr);
1475         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1476     }
1477 }
1478
1479 void
1480 KeepAlive()
1481 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1482     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1483     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1484     SendToICS("date\n");
1485     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1486 }
1487
1488 /* added routine for printf style output to ics */
1489 void ics_printf(char *format, ...)
1490 {
1491     char buffer[MSG_SIZ];
1492     va_list args;
1493
1494     va_start(args, format);
1495     vsnprintf(buffer, sizeof(buffer), format, args);
1496     buffer[sizeof(buffer)-1] = '\0';
1497     SendToICS(buffer);
1498     va_end(args);
1499 }
1500
1501 void
1502 SendToICS(s)
1503      char *s;
1504 {
1505     int count, outCount, outError;
1506
1507     if (icsPR == NULL) return;
1508
1509     count = strlen(s);
1510     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1511     if (outCount < count) {
1512         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1513     }
1514 }
1515
1516 /* This is used for sending logon scripts to the ICS. Sending
1517    without a delay causes problems when using timestamp on ICC
1518    (at least on my machine). */
1519 void
1520 SendToICSDelayed(s,msdelay)
1521      char *s;
1522      long msdelay;
1523 {
1524     int count, outCount, outError;
1525
1526     if (icsPR == NULL) return;
1527
1528     count = strlen(s);
1529     if (appData.debugMode) {
1530         fprintf(debugFP, ">ICS: ");
1531         show_bytes(debugFP, s, count);
1532         fprintf(debugFP, "\n");
1533     }
1534     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1535                                       msdelay);
1536     if (outCount < count) {
1537         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538     }
1539 }
1540
1541
1542 /* Remove all highlighting escape sequences in s
1543    Also deletes any suffix starting with '(' 
1544    */
1545 char *
1546 StripHighlightAndTitle(s)
1547      char *s;
1548 {
1549     static char retbuf[MSG_SIZ];
1550     char *p = retbuf;
1551
1552     while (*s != NULLCHAR) {
1553         while (*s == '\033') {
1554             while (*s != NULLCHAR && !isalpha(*s)) s++;
1555             if (*s != NULLCHAR) s++;
1556         }
1557         while (*s != NULLCHAR && *s != '\033') {
1558             if (*s == '(' || *s == '[') {
1559                 *p = NULLCHAR;
1560                 return retbuf;
1561             }
1562             *p++ = *s++;
1563         }
1564     }
1565     *p = NULLCHAR;
1566     return retbuf;
1567 }
1568
1569 /* Remove all highlighting escape sequences in s */
1570 char *
1571 StripHighlight(s)
1572      char *s;
1573 {
1574     static char retbuf[MSG_SIZ];
1575     char *p = retbuf;
1576
1577     while (*s != NULLCHAR) {
1578         while (*s == '\033') {
1579             while (*s != NULLCHAR && !isalpha(*s)) s++;
1580             if (*s != NULLCHAR) s++;
1581         }
1582         while (*s != NULLCHAR && *s != '\033') {
1583             *p++ = *s++;
1584         }
1585     }
1586     *p = NULLCHAR;
1587     return retbuf;
1588 }
1589
1590 char *variantNames[] = VARIANT_NAMES;
1591 char *
1592 VariantName(v)
1593      VariantClass v;
1594 {
1595     return variantNames[v];
1596 }
1597
1598
1599 /* Identify a variant from the strings the chess servers use or the
1600    PGN Variant tag names we use. */
1601 VariantClass
1602 StringToVariant(e)
1603      char *e;
1604 {
1605     char *p;
1606     int wnum = -1;
1607     VariantClass v = VariantNormal;
1608     int i, found = FALSE;
1609     char buf[MSG_SIZ];
1610
1611     if (!e) return v;
1612
1613     /* [HGM] skip over optional board-size prefixes */
1614     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1615         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1616         while( *e++ != '_');
1617     }
1618
1619     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1620         v = VariantNormal;
1621         found = TRUE;
1622     } else
1623     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1624       if (StrCaseStr(e, variantNames[i])) {
1625         v = (VariantClass) i;
1626         found = TRUE;
1627         break;
1628       }
1629     }
1630
1631     if (!found) {
1632       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1633           || StrCaseStr(e, "wild/fr") 
1634           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1635         v = VariantFischeRandom;
1636       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1637                  (i = 1, p = StrCaseStr(e, "w"))) {
1638         p += i;
1639         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1640         if (isdigit(*p)) {
1641           wnum = atoi(p);
1642         } else {
1643           wnum = -1;
1644         }
1645         switch (wnum) {
1646         case 0: /* FICS only, actually */
1647         case 1:
1648           /* Castling legal even if K starts on d-file */
1649           v = VariantWildCastle;
1650           break;
1651         case 2:
1652         case 3:
1653         case 4:
1654           /* Castling illegal even if K & R happen to start in
1655              normal positions. */
1656           v = VariantNoCastle;
1657           break;
1658         case 5:
1659         case 7:
1660         case 8:
1661         case 10:
1662         case 11:
1663         case 12:
1664         case 13:
1665         case 14:
1666         case 15:
1667         case 18:
1668         case 19:
1669           /* Castling legal iff K & R start in normal positions */
1670           v = VariantNormal;
1671           break;
1672         case 6:
1673         case 20:
1674         case 21:
1675           /* Special wilds for position setup; unclear what to do here */
1676           v = VariantLoadable;
1677           break;
1678         case 9:
1679           /* Bizarre ICC game */
1680           v = VariantTwoKings;
1681           break;
1682         case 16:
1683           v = VariantKriegspiel;
1684           break;
1685         case 17:
1686           v = VariantLosers;
1687           break;
1688         case 22:
1689           v = VariantFischeRandom;
1690           break;
1691         case 23:
1692           v = VariantCrazyhouse;
1693           break;
1694         case 24:
1695           v = VariantBughouse;
1696           break;
1697         case 25:
1698           v = Variant3Check;
1699           break;
1700         case 26:
1701           /* Not quite the same as FICS suicide! */
1702           v = VariantGiveaway;
1703           break;
1704         case 27:
1705           v = VariantAtomic;
1706           break;
1707         case 28:
1708           v = VariantShatranj;
1709           break;
1710
1711         /* Temporary names for future ICC types.  The name *will* change in 
1712            the next xboard/WinBoard release after ICC defines it. */
1713         case 29:
1714           v = Variant29;
1715           break;
1716         case 30:
1717           v = Variant30;
1718           break;
1719         case 31:
1720           v = Variant31;
1721           break;
1722         case 32:
1723           v = Variant32;
1724           break;
1725         case 33:
1726           v = Variant33;
1727           break;
1728         case 34:
1729           v = Variant34;
1730           break;
1731         case 35:
1732           v = Variant35;
1733           break;
1734         case 36:
1735           v = Variant36;
1736           break;
1737         case 37:
1738           v = VariantShogi;
1739           break;
1740         case 38:
1741           v = VariantXiangqi;
1742           break;
1743         case 39:
1744           v = VariantCourier;
1745           break;
1746         case 40:
1747           v = VariantGothic;
1748           break;
1749         case 41:
1750           v = VariantCapablanca;
1751           break;
1752         case 42:
1753           v = VariantKnightmate;
1754           break;
1755         case 43:
1756           v = VariantFairy;
1757           break;
1758         case 44:
1759           v = VariantCylinder;
1760           break;
1761         case 45:
1762           v = VariantFalcon;
1763           break;
1764         case 46:
1765           v = VariantCapaRandom;
1766           break;
1767         case 47:
1768           v = VariantBerolina;
1769           break;
1770         case 48:
1771           v = VariantJanus;
1772           break;
1773         case 49:
1774           v = VariantSuper;
1775           break;
1776         case 50:
1777           v = VariantGreat;
1778           break;
1779         case -1:
1780           /* Found "wild" or "w" in the string but no number;
1781              must assume it's normal chess. */
1782           v = VariantNormal;
1783           break;
1784         default:
1785           sprintf(buf, _("Unknown wild type %d"), wnum);
1786           DisplayError(buf, 0);
1787           v = VariantUnknown;
1788           break;
1789         }
1790       }
1791     }
1792     if (appData.debugMode) {
1793       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1794               e, wnum, VariantName(v));
1795     }
1796     return v;
1797 }
1798
1799 static int leftover_start = 0, leftover_len = 0;
1800 char star_match[STAR_MATCH_N][MSG_SIZ];
1801
1802 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1803    advance *index beyond it, and set leftover_start to the new value of
1804    *index; else return FALSE.  If pattern contains the character '*', it
1805    matches any sequence of characters not containing '\r', '\n', or the
1806    character following the '*' (if any), and the matched sequence(s) are
1807    copied into star_match.
1808    */
1809 int
1810 looking_at(buf, index, pattern)
1811      char *buf;
1812      int *index;
1813      char *pattern;
1814 {
1815     char *bufp = &buf[*index], *patternp = pattern;
1816     int star_count = 0;
1817     char *matchp = star_match[0];
1818     
1819     for (;;) {
1820         if (*patternp == NULLCHAR) {
1821             *index = leftover_start = bufp - buf;
1822             *matchp = NULLCHAR;
1823             return TRUE;
1824         }
1825         if (*bufp == NULLCHAR) return FALSE;
1826         if (*patternp == '*') {
1827             if (*bufp == *(patternp + 1)) {
1828                 *matchp = NULLCHAR;
1829                 matchp = star_match[++star_count];
1830                 patternp += 2;
1831                 bufp++;
1832                 continue;
1833             } else if (*bufp == '\n' || *bufp == '\r') {
1834                 patternp++;
1835                 if (*patternp == NULLCHAR)
1836                   continue;
1837                 else
1838                   return FALSE;
1839             } else {
1840                 *matchp++ = *bufp++;
1841                 continue;
1842             }
1843         }
1844         if (*patternp != *bufp) return FALSE;
1845         patternp++;
1846         bufp++;
1847     }
1848 }
1849
1850 void
1851 SendToPlayer(data, length)
1852      char *data;
1853      int length;
1854 {
1855     int error, outCount;
1856     outCount = OutputToProcess(NoProc, data, length, &error);
1857     if (outCount < length) {
1858         DisplayFatalError(_("Error writing to display"), error, 1);
1859     }
1860 }
1861
1862 void
1863 PackHolding(packed, holding)
1864      char packed[];
1865      char *holding;
1866 {
1867     char *p = holding;
1868     char *q = packed;
1869     int runlength = 0;
1870     int curr = 9999;
1871     do {
1872         if (*p == curr) {
1873             runlength++;
1874         } else {
1875             switch (runlength) {
1876               case 0:
1877                 break;
1878               case 1:
1879                 *q++ = curr;
1880                 break;
1881               case 2:
1882                 *q++ = curr;
1883                 *q++ = curr;
1884                 break;
1885               default:
1886                 sprintf(q, "%d", runlength);
1887                 while (*q) q++;
1888                 *q++ = curr;
1889                 break;
1890             }
1891             runlength = 1;
1892             curr = *p;
1893         }
1894     } while (*p++);
1895     *q = NULLCHAR;
1896 }
1897
1898 /* Telnet protocol requests from the front end */
1899 void
1900 TelnetRequest(ddww, option)
1901      unsigned char ddww, option;
1902 {
1903     unsigned char msg[3];
1904     int outCount, outError;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1907
1908     if (appData.debugMode) {
1909         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1910         switch (ddww) {
1911           case TN_DO:
1912             ddwwStr = "DO";
1913             break;
1914           case TN_DONT:
1915             ddwwStr = "DONT";
1916             break;
1917           case TN_WILL:
1918             ddwwStr = "WILL";
1919             break;
1920           case TN_WONT:
1921             ddwwStr = "WONT";
1922             break;
1923           default:
1924             ddwwStr = buf1;
1925             sprintf(buf1, "%d", ddww);
1926             break;
1927         }
1928         switch (option) {
1929           case TN_ECHO:
1930             optionStr = "ECHO";
1931             break;
1932           default:
1933             optionStr = buf2;
1934             sprintf(buf2, "%d", option);
1935             break;
1936         }
1937         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1938     }
1939     msg[0] = TN_IAC;
1940     msg[1] = ddww;
1941     msg[2] = option;
1942     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1943     if (outCount < 3) {
1944         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1945     }
1946 }
1947
1948 void
1949 DoEcho()
1950 {
1951     if (!appData.icsActive) return;
1952     TelnetRequest(TN_DO, TN_ECHO);
1953 }
1954
1955 void
1956 DontEcho()
1957 {
1958     if (!appData.icsActive) return;
1959     TelnetRequest(TN_DONT, TN_ECHO);
1960 }
1961
1962 void
1963 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1964 {
1965     /* put the holdings sent to us by the server on the board holdings area */
1966     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1967     char p;
1968     ChessSquare piece;
1969
1970     if(gameInfo.holdingsWidth < 2)  return;
1971     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1972         return; // prevent overwriting by pre-board holdings
1973
1974     if( (int)lowestPiece >= BlackPawn ) {
1975         holdingsColumn = 0;
1976         countsColumn = 1;
1977         holdingsStartRow = BOARD_HEIGHT-1;
1978         direction = -1;
1979     } else {
1980         holdingsColumn = BOARD_WIDTH-1;
1981         countsColumn = BOARD_WIDTH-2;
1982         holdingsStartRow = 0;
1983         direction = 1;
1984     }
1985
1986     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1987         board[i][holdingsColumn] = EmptySquare;
1988         board[i][countsColumn]   = (ChessSquare) 0;
1989     }
1990     while( (p=*holdings++) != NULLCHAR ) {
1991         piece = CharToPiece( ToUpper(p) );
1992         if(piece == EmptySquare) continue;
1993         /*j = (int) piece - (int) WhitePawn;*/
1994         j = PieceToNumber(piece);
1995         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1996         if(j < 0) continue;               /* should not happen */
1997         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1998         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1999         board[holdingsStartRow+j*direction][countsColumn]++;
2000     }
2001 }
2002
2003
2004 void
2005 VariantSwitch(Board board, VariantClass newVariant)
2006 {
2007    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2008    static Board oldBoard;
2009
2010    startedFromPositionFile = FALSE;
2011    if(gameInfo.variant == newVariant) return;
2012
2013    /* [HGM] This routine is called each time an assignment is made to
2014     * gameInfo.variant during a game, to make sure the board sizes
2015     * are set to match the new variant. If that means adding or deleting
2016     * holdings, we shift the playing board accordingly
2017     * This kludge is needed because in ICS observe mode, we get boards
2018     * of an ongoing game without knowing the variant, and learn about the
2019     * latter only later. This can be because of the move list we requested,
2020     * in which case the game history is refilled from the beginning anyway,
2021     * but also when receiving holdings of a crazyhouse game. In the latter
2022     * case we want to add those holdings to the already received position.
2023     */
2024
2025    
2026    if (appData.debugMode) {
2027      fprintf(debugFP, "Switch board from %s to %s\n",
2028              VariantName(gameInfo.variant), VariantName(newVariant));
2029      setbuf(debugFP, NULL);
2030    }
2031    shuffleOpenings = 0;       /* [HGM] shuffle */
2032    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2033    switch(newVariant) 
2034      {
2035      case VariantShogi:
2036        newWidth = 9;  newHeight = 9;
2037        gameInfo.holdingsSize = 7;
2038      case VariantBughouse:
2039      case VariantCrazyhouse:
2040        newHoldingsWidth = 2; break;
2041      case VariantGreat:
2042        newWidth = 10;
2043      case VariantSuper:
2044        newHoldingsWidth = 2;
2045        gameInfo.holdingsSize = 8;
2046        break;
2047      case VariantGothic:
2048      case VariantCapablanca:
2049      case VariantCapaRandom:
2050        newWidth = 10;
2051      default:
2052        newHoldingsWidth = gameInfo.holdingsSize = 0;
2053      };
2054    
2055    if(newWidth  != gameInfo.boardWidth  ||
2056       newHeight != gameInfo.boardHeight ||
2057       newHoldingsWidth != gameInfo.holdingsWidth ) {
2058      
2059      /* shift position to new playing area, if needed */
2060      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2061        for(i=0; i<BOARD_HEIGHT; i++) 
2062          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2063            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064              board[i][j];
2065        for(i=0; i<newHeight; i++) {
2066          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2067          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2068        }
2069      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2070        for(i=0; i<BOARD_HEIGHT; i++)
2071          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2072            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2073              board[i][j];
2074      }
2075      gameInfo.boardWidth  = newWidth;
2076      gameInfo.boardHeight = newHeight;
2077      gameInfo.holdingsWidth = newHoldingsWidth;
2078      gameInfo.variant = newVariant;
2079      InitDrawingSizes(-2, 0);
2080    } else gameInfo.variant = newVariant;
2081    CopyBoard(oldBoard, board);   // remember correctly formatted board
2082      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2083    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2084 }
2085
2086 static int loggedOn = FALSE;
2087
2088 /*-- Game start info cache: --*/
2089 int gs_gamenum;
2090 char gs_kind[MSG_SIZ];
2091 static char player1Name[128] = "";
2092 static char player2Name[128] = "";
2093 static char cont_seq[] = "\n\\   ";
2094 static int player1Rating = -1;
2095 static int player2Rating = -1;
2096 /*----------------------------*/
2097
2098 ColorClass curColor = ColorNormal;
2099 int suppressKibitz = 0;
2100
2101 // [HGM] seekgraph
2102 Boolean soughtPending = FALSE;
2103 Boolean seekGraphUp;
2104 #define MAX_SEEK_ADS 200
2105 #define SQUARE 0x80
2106 char *seekAdList[MAX_SEEK_ADS];
2107 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2108 float tcList[MAX_SEEK_ADS];
2109 char colorList[MAX_SEEK_ADS];
2110 int nrOfSeekAds = 0;
2111 int minRating = 1010, maxRating = 2800;
2112 int hMargin = 10, vMargin = 20, h, w;
2113 extern int squareSize, lineGap;
2114
2115 void
2116 PlotSeekAd(int i)
2117 {
2118         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2119         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2120         if(r < minRating+100 && r >=0 ) r = minRating+100;
2121         if(r > maxRating) r = maxRating;
2122         if(tc < 1.) tc = 1.;
2123         if(tc > 95.) tc = 95.;
2124         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2125         y = ((double)r - minRating)/(maxRating - minRating)
2126             * (h-vMargin-squareSize/8-1) + vMargin;
2127         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2128         if(strstr(seekAdList[i], " u ")) color = 1;
2129         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2130            !strstr(seekAdList[i], "bullet") &&
2131            !strstr(seekAdList[i], "blitz") &&
2132            !strstr(seekAdList[i], "standard") ) color = 2;
2133         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2134         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2135 }
2136
2137 void
2138 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2139 {
2140         char buf[MSG_SIZ], *ext = "";
2141         VariantClass v = StringToVariant(type);
2142         if(strstr(type, "wild")) {
2143             ext = type + 4; // append wild number
2144             if(v == VariantFischeRandom) type = "chess960"; else
2145             if(v == VariantLoadable) type = "setup"; else
2146             type = VariantName(v);
2147         }
2148         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2149         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2150             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2151             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2152             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2153             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2154             seekNrList[nrOfSeekAds] = nr;
2155             zList[nrOfSeekAds] = 0;
2156             seekAdList[nrOfSeekAds++] = StrSave(buf);
2157             if(plot) PlotSeekAd(nrOfSeekAds-1);
2158         }
2159 }
2160
2161 void
2162 EraseSeekDot(int i)
2163 {
2164     int x = xList[i], y = yList[i], d=squareSize/4, k;
2165     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2166     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2167     // now replot every dot that overlapped
2168     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2169         int xx = xList[k], yy = yList[k];
2170         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2171             DrawSeekDot(xx, yy, colorList[k]);
2172     }
2173 }
2174
2175 void
2176 RemoveSeekAd(int nr)
2177 {
2178         int i;
2179         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2180             EraseSeekDot(i);
2181             if(seekAdList[i]) free(seekAdList[i]);
2182             seekAdList[i] = seekAdList[--nrOfSeekAds];
2183             seekNrList[i] = seekNrList[nrOfSeekAds];
2184             ratingList[i] = ratingList[nrOfSeekAds];
2185             colorList[i]  = colorList[nrOfSeekAds];
2186             tcList[i] = tcList[nrOfSeekAds];
2187             xList[i]  = xList[nrOfSeekAds];
2188             yList[i]  = yList[nrOfSeekAds];
2189             zList[i]  = zList[nrOfSeekAds];
2190             seekAdList[nrOfSeekAds] = NULL;
2191             break;
2192         }
2193 }
2194
2195 Boolean
2196 MatchSoughtLine(char *line)
2197 {
2198     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2199     int nr, base, inc, u=0; char dummy;
2200
2201     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2202        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2203        (u=1) &&
2204        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2205         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2206         // match: compact and save the line
2207         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2208         return TRUE;
2209     }
2210     return FALSE;
2211 }
2212
2213 int
2214 DrawSeekGraph()
2215 {
2216     int i;
2217     if(!seekGraphUp) return FALSE;
2218     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2219     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2220
2221     DrawSeekBackground(0, 0, w, h);
2222     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2223     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2224     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2225         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2226         yy = h-1-yy;
2227         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2228         if(i%500 == 0) {
2229             char buf[MSG_SIZ];
2230             sprintf(buf, "%d", i);
2231             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2232         }
2233     }
2234     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2235     for(i=1; i<100; i+=(i<10?1:5)) {
2236         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2237         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2238         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2239             char buf[MSG_SIZ];
2240             sprintf(buf, "%d", i);
2241             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2242         }
2243     }
2244     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2245     return TRUE;
2246 }
2247
2248 int SeekGraphClick(ClickType click, int x, int y, int moving)
2249 {
2250     static int lastDown = 0, displayed = 0, lastSecond;
2251     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2252         if(click == Release || moving) return FALSE;
2253         nrOfSeekAds = 0;
2254         soughtPending = TRUE;
2255         SendToICS(ics_prefix);
2256         SendToICS("sought\n"); // should this be "sought all"?
2257     } else { // issue challenge based on clicked ad
2258         int dist = 10000; int i, closest = 0, second = 0;
2259         for(i=0; i<nrOfSeekAds; i++) {
2260             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2261             if(d < dist) { dist = d; closest = i; }
2262             second += (d - zList[i] < 120); // count in-range ads
2263             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2264         }
2265         if(dist < 120) {
2266             char buf[MSG_SIZ];
2267             second = (second > 1);
2268             if(displayed != closest || second != lastSecond) {
2269                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2270                 lastSecond = second; displayed = closest;
2271             }
2272             if(click == Press) {
2273                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2274                 lastDown = closest;
2275                 return TRUE;
2276             } // on press 'hit', only show info
2277             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2278             sprintf(buf, "play %d\n", seekNrList[closest]);
2279             SendToICS(ics_prefix);
2280             SendToICS(buf);
2281             return TRUE; // let incoming board of started game pop down the graph
2282         } else if(click == Release) { // release 'miss' is ignored
2283             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2284             if(moving == 2) { // right up-click
2285                 nrOfSeekAds = 0; // refresh graph
2286                 soughtPending = TRUE;
2287                 SendToICS(ics_prefix);
2288                 SendToICS("sought\n"); // should this be "sought all"?
2289             }
2290             return TRUE;
2291         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2292         // press miss or release hit 'pop down' seek graph
2293         seekGraphUp = FALSE;
2294         DrawPosition(TRUE, NULL);
2295     }
2296     return TRUE;
2297 }
2298
2299 void
2300 read_from_ics(isr, closure, data, count, error)
2301      InputSourceRef isr;
2302      VOIDSTAR closure;
2303      char *data;
2304      int count;
2305      int error;
2306 {
2307 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2308 #define STARTED_NONE 0
2309 #define STARTED_MOVES 1
2310 #define STARTED_BOARD 2
2311 #define STARTED_OBSERVE 3
2312 #define STARTED_HOLDINGS 4
2313 #define STARTED_CHATTER 5
2314 #define STARTED_COMMENT 6
2315 #define STARTED_MOVES_NOHIDE 7
2316     
2317     static int started = STARTED_NONE;
2318     static char parse[20000];
2319     static int parse_pos = 0;
2320     static char buf[BUF_SIZE + 1];
2321     static int firstTime = TRUE, intfSet = FALSE;
2322     static ColorClass prevColor = ColorNormal;
2323     static int savingComment = FALSE;
2324     static int cmatch = 0; // continuation sequence match
2325     char *bp;
2326     char str[500];
2327     int i, oldi;
2328     int buf_len;
2329     int next_out;
2330     int tkind;
2331     int backup;    /* [DM] For zippy color lines */
2332     char *p;
2333     char talker[MSG_SIZ]; // [HGM] chat
2334     int channel;
2335
2336     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2337
2338     if (appData.debugMode) {
2339       if (!error) {
2340         fprintf(debugFP, "<ICS: ");
2341         show_bytes(debugFP, data, count);
2342         fprintf(debugFP, "\n");
2343       }
2344     }
2345
2346     if (appData.debugMode) { int f = forwardMostMove;
2347         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2348                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2349                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2350     }
2351     if (count > 0) {
2352         /* If last read ended with a partial line that we couldn't parse,
2353            prepend it to the new read and try again. */
2354         if (leftover_len > 0) {
2355             for (i=0; i<leftover_len; i++)
2356               buf[i] = buf[leftover_start + i];
2357         }
2358
2359     /* copy new characters into the buffer */
2360     bp = buf + leftover_len;
2361     buf_len=leftover_len;
2362     for (i=0; i<count; i++)
2363     {
2364         // ignore these
2365         if (data[i] == '\r')
2366             continue;
2367
2368         // join lines split by ICS?
2369         if (!appData.noJoin)
2370         {
2371             /*
2372                 Joining just consists of finding matches against the
2373                 continuation sequence, and discarding that sequence
2374                 if found instead of copying it.  So, until a match
2375                 fails, there's nothing to do since it might be the
2376                 complete sequence, and thus, something we don't want
2377                 copied.
2378             */
2379             if (data[i] == cont_seq[cmatch])
2380             {
2381                 cmatch++;
2382                 if (cmatch == strlen(cont_seq))
2383                 {
2384                     cmatch = 0; // complete match.  just reset the counter
2385
2386                     /*
2387                         it's possible for the ICS to not include the space
2388                         at the end of the last word, making our [correct]
2389                         join operation fuse two separate words.  the server
2390                         does this when the space occurs at the width setting.
2391                     */
2392                     if (!buf_len || buf[buf_len-1] != ' ')
2393                     {
2394                         *bp++ = ' ';
2395                         buf_len++;
2396                     }
2397                 }
2398                 continue;
2399             }
2400             else if (cmatch)
2401             {
2402                 /*
2403                     match failed, so we have to copy what matched before
2404                     falling through and copying this character.  In reality,
2405                     this will only ever be just the newline character, but
2406                     it doesn't hurt to be precise.
2407                 */
2408                 strncpy(bp, cont_seq, cmatch);
2409                 bp += cmatch;
2410                 buf_len += cmatch;
2411                 cmatch = 0;
2412             }
2413         }
2414
2415         // copy this char
2416         *bp++ = data[i];
2417         buf_len++;
2418     }
2419
2420         buf[buf_len] = NULLCHAR;
2421 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2422         next_out = 0;
2423         leftover_start = 0;
2424         
2425         i = 0;
2426         while (i < buf_len) {
2427             /* Deal with part of the TELNET option negotiation
2428                protocol.  We refuse to do anything beyond the
2429                defaults, except that we allow the WILL ECHO option,
2430                which ICS uses to turn off password echoing when we are
2431                directly connected to it.  We reject this option
2432                if localLineEditing mode is on (always on in xboard)
2433                and we are talking to port 23, which might be a real
2434                telnet server that will try to keep WILL ECHO on permanently.
2435              */
2436             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2437                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2438                 unsigned char option;
2439                 oldi = i;
2440                 switch ((unsigned char) buf[++i]) {
2441                   case TN_WILL:
2442                     if (appData.debugMode)
2443                       fprintf(debugFP, "\n<WILL ");
2444                     switch (option = (unsigned char) buf[++i]) {
2445                       case TN_ECHO:
2446                         if (appData.debugMode)
2447                           fprintf(debugFP, "ECHO ");
2448                         /* Reply only if this is a change, according
2449                            to the protocol rules. */
2450                         if (remoteEchoOption) break;
2451                         if (appData.localLineEditing &&
2452                             atoi(appData.icsPort) == TN_PORT) {
2453                             TelnetRequest(TN_DONT, TN_ECHO);
2454                         } else {
2455                             EchoOff();
2456                             TelnetRequest(TN_DO, TN_ECHO);
2457                             remoteEchoOption = TRUE;
2458                         }
2459                         break;
2460                       default:
2461                         if (appData.debugMode)
2462                           fprintf(debugFP, "%d ", option);
2463                         /* Whatever this is, we don't want it. */
2464                         TelnetRequest(TN_DONT, option);
2465                         break;
2466                     }
2467                     break;
2468                   case TN_WONT:
2469                     if (appData.debugMode)
2470                       fprintf(debugFP, "\n<WONT ");
2471                     switch (option = (unsigned char) buf[++i]) {
2472                       case TN_ECHO:
2473                         if (appData.debugMode)
2474                           fprintf(debugFP, "ECHO ");
2475                         /* Reply only if this is a change, according
2476                            to the protocol rules. */
2477                         if (!remoteEchoOption) break;
2478                         EchoOn();
2479                         TelnetRequest(TN_DONT, TN_ECHO);
2480                         remoteEchoOption = FALSE;
2481                         break;
2482                       default:
2483                         if (appData.debugMode)
2484                           fprintf(debugFP, "%d ", (unsigned char) option);
2485                         /* Whatever this is, it must already be turned
2486                            off, because we never agree to turn on
2487                            anything non-default, so according to the
2488                            protocol rules, we don't reply. */
2489                         break;
2490                     }
2491                     break;
2492                   case TN_DO:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<DO ");
2495                     switch (option = (unsigned char) buf[++i]) {
2496                       default:
2497                         /* Whatever this is, we refuse to do it. */
2498                         if (appData.debugMode)
2499                           fprintf(debugFP, "%d ", option);
2500                         TelnetRequest(TN_WONT, option);
2501                         break;
2502                     }
2503                     break;
2504                   case TN_DONT:
2505                     if (appData.debugMode)
2506                       fprintf(debugFP, "\n<DONT ");
2507                     switch (option = (unsigned char) buf[++i]) {
2508                       default:
2509                         if (appData.debugMode)
2510                           fprintf(debugFP, "%d ", option);
2511                         /* Whatever this is, we are already not doing
2512                            it, because we never agree to do anything
2513                            non-default, so according to the protocol
2514                            rules, we don't reply. */
2515                         break;
2516                     }
2517                     break;
2518                   case TN_IAC:
2519                     if (appData.debugMode)
2520                       fprintf(debugFP, "\n<IAC ");
2521                     /* Doubled IAC; pass it through */
2522                     i--;
2523                     break;
2524                   default:
2525                     if (appData.debugMode)
2526                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2527                     /* Drop all other telnet commands on the floor */
2528                     break;
2529                 }
2530                 if (oldi > next_out)
2531                   SendToPlayer(&buf[next_out], oldi - next_out);
2532                 if (++i > next_out)
2533                   next_out = i;
2534                 continue;
2535             }
2536                 
2537             /* OK, this at least will *usually* work */
2538             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2539                 loggedOn = TRUE;
2540             }
2541             
2542             if (loggedOn && !intfSet) {
2543                 if (ics_type == ICS_ICC) {
2544                   sprintf(str,
2545                           "/set-quietly interface %s\n/set-quietly style 12\n",
2546                           programVersion);
2547                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2548                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2549                 } else if (ics_type == ICS_CHESSNET) {
2550                   sprintf(str, "/style 12\n");
2551                 } else {
2552                   strcpy(str, "alias $ @\n$set interface ");
2553                   strcat(str, programVersion);
2554                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2555                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2556                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2557 #ifdef WIN32
2558                   strcat(str, "$iset nohighlight 1\n");
2559 #endif
2560                   strcat(str, "$iset lock 1\n$style 12\n");
2561                 }
2562                 SendToICS(str);
2563                 NotifyFrontendLogin();
2564                 intfSet = TRUE;
2565             }
2566
2567             if (started == STARTED_COMMENT) {
2568                 /* Accumulate characters in comment */
2569                 parse[parse_pos++] = buf[i];
2570                 if (buf[i] == '\n') {
2571                     parse[parse_pos] = NULLCHAR;
2572                     if(chattingPartner>=0) {
2573                         char mess[MSG_SIZ];
2574                         sprintf(mess, "%s%s", talker, parse);
2575                         OutputChatMessage(chattingPartner, mess);
2576                         chattingPartner = -1;
2577                         next_out = i+1; // [HGM] suppress printing in ICS window
2578                     } else
2579                     if(!suppressKibitz) // [HGM] kibitz
2580                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2581                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2582                         int nrDigit = 0, nrAlph = 0, j;
2583                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2584                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2585                         parse[parse_pos] = NULLCHAR;
2586                         // try to be smart: if it does not look like search info, it should go to
2587                         // ICS interaction window after all, not to engine-output window.
2588                         for(j=0; j<parse_pos; j++) { // count letters and digits
2589                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2590                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2591                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2592                         }
2593                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2594                             int depth=0; float score;
2595                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2596                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2597                                 pvInfoList[forwardMostMove-1].depth = depth;
2598                                 pvInfoList[forwardMostMove-1].score = 100*score;
2599                             }
2600                             OutputKibitz(suppressKibitz, parse);
2601                         } else {
2602                             char tmp[MSG_SIZ];
2603                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2604                             SendToPlayer(tmp, strlen(tmp));
2605                         }
2606                         next_out = i+1; // [HGM] suppress printing in ICS window
2607                     }
2608                     started = STARTED_NONE;
2609                 } else {
2610                     /* Don't match patterns against characters in comment */
2611                     i++;
2612                     continue;
2613                 }
2614             }
2615             if (started == STARTED_CHATTER) {
2616                 if (buf[i] != '\n') {
2617                     /* Don't match patterns against characters in chatter */
2618                     i++;
2619                     continue;
2620                 }
2621                 started = STARTED_NONE;
2622                 if(suppressKibitz) next_out = i+1;
2623             }
2624
2625             /* Kludge to deal with rcmd protocol */
2626             if (firstTime && looking_at(buf, &i, "\001*")) {
2627                 DisplayFatalError(&buf[1], 0, 1);
2628                 continue;
2629             } else {
2630                 firstTime = FALSE;
2631             }
2632
2633             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2634                 ics_type = ICS_ICC;
2635                 ics_prefix = "/";
2636                 if (appData.debugMode)
2637                   fprintf(debugFP, "ics_type %d\n", ics_type);
2638                 continue;
2639             }
2640             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2641                 ics_type = ICS_FICS;
2642                 ics_prefix = "$";
2643                 if (appData.debugMode)
2644                   fprintf(debugFP, "ics_type %d\n", ics_type);
2645                 continue;
2646             }
2647             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2648                 ics_type = ICS_CHESSNET;
2649                 ics_prefix = "/";
2650                 if (appData.debugMode)
2651                   fprintf(debugFP, "ics_type %d\n", ics_type);
2652                 continue;
2653             }
2654
2655             if (!loggedOn &&
2656                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2657                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2658                  looking_at(buf, &i, "will be \"*\""))) {
2659               strcpy(ics_handle, star_match[0]);
2660               continue;
2661             }
2662
2663             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2664               char buf[MSG_SIZ];
2665               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2666               DisplayIcsInteractionTitle(buf);
2667               have_set_title = TRUE;
2668             }
2669
2670             /* skip finger notes */
2671             if (started == STARTED_NONE &&
2672                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2673                  (buf[i] == '1' && buf[i+1] == '0')) &&
2674                 buf[i+2] == ':' && buf[i+3] == ' ') {
2675               started = STARTED_CHATTER;
2676               i += 3;
2677               continue;
2678             }
2679
2680             oldi = i;
2681             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2682             if(appData.seekGraph) {
2683                 if(soughtPending && MatchSoughtLine(buf+i)) {
2684                     i = strstr(buf+i, "rated") - buf;
2685                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                     next_out = leftover_start = i;
2687                     started = STARTED_CHATTER;
2688                     suppressKibitz = TRUE;
2689                     continue;
2690                 }
2691                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2692                         && looking_at(buf, &i, "* ads displayed")) {
2693                     soughtPending = FALSE;
2694                     seekGraphUp = TRUE;
2695                     DrawSeekGraph();
2696                     continue;
2697                 }
2698                 if(appData.autoRefresh) {
2699                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2700                         int s = (ics_type == ICS_ICC); // ICC format differs
2701                         if(seekGraphUp)
2702                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2703                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2704                         looking_at(buf, &i, "*% "); // eat prompt
2705                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2706                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2707                         next_out = i; // suppress
2708                         continue;
2709                     }
2710                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2711                         char *p = star_match[0];
2712                         while(*p) {
2713                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2714                             while(*p && *p++ != ' '); // next
2715                         }
2716                         looking_at(buf, &i, "*% "); // eat prompt
2717                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2718                         next_out = i;
2719                         continue;
2720                     }
2721                 }
2722             }
2723
2724             /* skip formula vars */
2725             if (started == STARTED_NONE &&
2726                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2727               started = STARTED_CHATTER;
2728               i += 3;
2729               continue;
2730             }
2731
2732             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2733             if (appData.autoKibitz && started == STARTED_NONE && 
2734                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2735                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2736                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2737                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2738                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2739                         suppressKibitz = TRUE;
2740                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2741                         next_out = i;
2742                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2743                                 && (gameMode == IcsPlayingWhite)) ||
2744                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2745                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2746                             started = STARTED_CHATTER; // own kibitz we simply discard
2747                         else {
2748                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2749                             parse_pos = 0; parse[0] = NULLCHAR;
2750                             savingComment = TRUE;
2751                             suppressKibitz = gameMode != IcsObserving ? 2 :
2752                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2753                         } 
2754                         continue;
2755                 } else
2756                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2757                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2758                          && atoi(star_match[0])) {
2759                     // suppress the acknowledgements of our own autoKibitz
2760                     char *p;
2761                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2762                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2763                     SendToPlayer(star_match[0], strlen(star_match[0]));
2764                     if(looking_at(buf, &i, "*% ")) // eat prompt
2765                         suppressKibitz = FALSE;
2766                     next_out = i;
2767                     continue;
2768                 }
2769             } // [HGM] kibitz: end of patch
2770
2771             // [HGM] chat: intercept tells by users for which we have an open chat window
2772             channel = -1;
2773             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2774                                            looking_at(buf, &i, "* whispers:") ||
2775                                            looking_at(buf, &i, "* kibitzes:") ||
2776                                            looking_at(buf, &i, "* shouts:") ||
2777                                            looking_at(buf, &i, "* c-shouts:") ||
2778                                            looking_at(buf, &i, "--> * ") ||
2779                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2780                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2781                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2782                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2783                 int p;
2784                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2785                 chattingPartner = -1;
2786
2787                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2788                 for(p=0; p<MAX_CHAT; p++) {
2789                     if(channel == atoi(chatPartner[p])) {
2790                     talker[0] = '['; strcat(talker, "] ");
2791                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2792                     chattingPartner = p; break;
2793                     }
2794                 } else
2795                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2796                 for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("kibitzes", chatPartner[p])) {
2798                         talker[0] = '['; strcat(talker, "] ");
2799                         chattingPartner = p; break;
2800                     }
2801                 } else
2802                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2803                 for(p=0; p<MAX_CHAT; p++) {
2804                     if(!strcmp("whispers", chatPartner[p])) {
2805                         talker[0] = '['; strcat(talker, "] ");
2806                         chattingPartner = p; break;
2807                     }
2808                 } else
2809                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2810                   if(buf[i-8] == '-' && buf[i-3] == 't')
2811                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2812                     if(!strcmp("c-shouts", chatPartner[p])) {
2813                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2814                         chattingPartner = p; break;
2815                     }
2816                   }
2817                   if(chattingPartner < 0)
2818                   for(p=0; p<MAX_CHAT; p++) {
2819                     if(!strcmp("shouts", chatPartner[p])) {
2820                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2821                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2822                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2823                         chattingPartner = p; break;
2824                     }
2825                   }
2826                 }
2827                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2828                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2829                     talker[0] = 0; Colorize(ColorTell, FALSE);
2830                     chattingPartner = p; break;
2831                 }
2832                 if(chattingPartner<0) i = oldi; else {
2833                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2834                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2835                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2836                     started = STARTED_COMMENT;
2837                     parse_pos = 0; parse[0] = NULLCHAR;
2838                     savingComment = 3 + chattingPartner; // counts as TRUE
2839                     suppressKibitz = TRUE;
2840                     continue;
2841                 }
2842             } // [HGM] chat: end of patch
2843
2844             if (appData.zippyTalk || appData.zippyPlay) {
2845                 /* [DM] Backup address for color zippy lines */
2846                 backup = i;
2847 #if ZIPPY
2848                if (loggedOn == TRUE)
2849                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2850                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2851 #endif
2852             } // [DM] 'else { ' deleted
2853                 if (
2854                     /* Regular tells and says */
2855                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2856                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2857                     looking_at(buf, &i, "* says: ") ||
2858                     /* Don't color "message" or "messages" output */
2859                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2860                     looking_at(buf, &i, "*. * at *:*: ") ||
2861                     looking_at(buf, &i, "--* (*:*): ") ||
2862                     /* Message notifications (same color as tells) */
2863                     looking_at(buf, &i, "* has left a message ") ||
2864                     looking_at(buf, &i, "* just sent you a message:\n") ||
2865                     /* Whispers and kibitzes */
2866                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2867                     looking_at(buf, &i, "* kibitzes: ") ||
2868                     /* Channel tells */
2869                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870
2871                   if (tkind == 1 && strchr(star_match[0], ':')) {
2872                       /* Avoid "tells you:" spoofs in channels */
2873                      tkind = 3;
2874                   }
2875                   if (star_match[0][0] == NULLCHAR ||
2876                       strchr(star_match[0], ' ') ||
2877                       (tkind == 3 && strchr(star_match[1], ' '))) {
2878                     /* Reject bogus matches */
2879                     i = oldi;
2880                   } else {
2881                     if (appData.colorize) {
2882                       if (oldi > next_out) {
2883                         SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = oldi;
2885                       }
2886                       switch (tkind) {
2887                       case 1:
2888                         Colorize(ColorTell, FALSE);
2889                         curColor = ColorTell;
2890                         break;
2891                       case 2:
2892                         Colorize(ColorKibitz, FALSE);
2893                         curColor = ColorKibitz;
2894                         break;
2895                       case 3:
2896                         p = strrchr(star_match[1], '(');
2897                         if (p == NULL) {
2898                           p = star_match[1];
2899                         } else {
2900                           p++;
2901                         }
2902                         if (atoi(p) == 1) {
2903                           Colorize(ColorChannel1, FALSE);
2904                           curColor = ColorChannel1;
2905                         } else {
2906                           Colorize(ColorChannel, FALSE);
2907                           curColor = ColorChannel;
2908                         }
2909                         break;
2910                       case 5:
2911                         curColor = ColorNormal;
2912                         break;
2913                       }
2914                     }
2915                     if (started == STARTED_NONE && appData.autoComment &&
2916                         (gameMode == IcsObserving ||
2917                          gameMode == IcsPlayingWhite ||
2918                          gameMode == IcsPlayingBlack)) {
2919                       parse_pos = i - oldi;
2920                       memcpy(parse, &buf[oldi], parse_pos);
2921                       parse[parse_pos] = NULLCHAR;
2922                       started = STARTED_COMMENT;
2923                       savingComment = TRUE;
2924                     } else {
2925                       started = STARTED_CHATTER;
2926                       savingComment = FALSE;
2927                     }
2928                     loggedOn = TRUE;
2929                     continue;
2930                   }
2931                 }
2932
2933                 if (looking_at(buf, &i, "* s-shouts: ") ||
2934                     looking_at(buf, &i, "* c-shouts: ")) {
2935                     if (appData.colorize) {
2936                         if (oldi > next_out) {
2937                             SendToPlayer(&buf[next_out], oldi - next_out);
2938                             next_out = oldi;
2939                         }
2940                         Colorize(ColorSShout, FALSE);
2941                         curColor = ColorSShout;
2942                     }
2943                     loggedOn = TRUE;
2944                     started = STARTED_CHATTER;
2945                     continue;
2946                 }
2947
2948                 if (looking_at(buf, &i, "--->")) {
2949                     loggedOn = TRUE;
2950                     continue;
2951                 }
2952
2953                 if (looking_at(buf, &i, "* shouts: ") ||
2954                     looking_at(buf, &i, "--> ")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorShout, FALSE);
2961                         curColor = ColorShout;
2962                     }
2963                     loggedOn = TRUE;
2964                     started = STARTED_CHATTER;
2965                     continue;
2966                 }
2967
2968                 if (looking_at( buf, &i, "Challenge:")) {
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorChallenge, FALSE);
2975                         curColor = ColorChallenge;
2976                     }
2977                     loggedOn = TRUE;
2978                     continue;
2979                 }
2980
2981                 if (looking_at(buf, &i, "* offers you") ||
2982                     looking_at(buf, &i, "* offers to be") ||
2983                     looking_at(buf, &i, "* would like to") ||
2984                     looking_at(buf, &i, "* requests to") ||
2985                     looking_at(buf, &i, "Your opponent offers") ||
2986                     looking_at(buf, &i, "Your opponent requests")) {
2987
2988                     if (appData.colorize) {
2989                         if (oldi > next_out) {
2990                             SendToPlayer(&buf[next_out], oldi - next_out);
2991                             next_out = oldi;
2992                         }
2993                         Colorize(ColorRequest, FALSE);
2994                         curColor = ColorRequest;
2995                     }
2996                     continue;
2997                 }
2998
2999                 if (looking_at(buf, &i, "* (*) seeking")) {
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorSeek, FALSE);
3006                         curColor = ColorSeek;
3007                     }
3008                     continue;
3009             }
3010
3011             if (looking_at(buf, &i, "\\   ")) {
3012                 if (prevColor != ColorNormal) {
3013                     if (oldi > next_out) {
3014                         SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = oldi;
3016                     }
3017                     Colorize(prevColor, TRUE);
3018                     curColor = prevColor;
3019                 }
3020                 if (savingComment) {
3021                     parse_pos = i - oldi;
3022                     memcpy(parse, &buf[oldi], parse_pos);
3023                     parse[parse_pos] = NULLCHAR;
3024                     started = STARTED_COMMENT;
3025                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3026                         chattingPartner = savingComment - 3; // kludge to remember the box
3027                 } else {
3028                     started = STARTED_CHATTER;
3029                 }
3030                 continue;
3031             }
3032
3033             if (looking_at(buf, &i, "Black Strength :") ||
3034                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3035                 looking_at(buf, &i, "<10>") ||
3036                 looking_at(buf, &i, "#@#")) {
3037                 /* Wrong board style */
3038                 loggedOn = TRUE;
3039                 SendToICS(ics_prefix);
3040                 SendToICS("set style 12\n");
3041                 SendToICS(ics_prefix);
3042                 SendToICS("refresh\n");
3043                 continue;
3044             }
3045             
3046             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047                 ICSInitScript();
3048                 have_sent_ICS_logon = 1;
3049                 continue;
3050             }
3051               
3052             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3053                 (looking_at(buf, &i, "\n<12> ") ||
3054                  looking_at(buf, &i, "<12> "))) {
3055                 loggedOn = TRUE;
3056                 if (oldi > next_out) {
3057                     SendToPlayer(&buf[next_out], oldi - next_out);
3058                 }
3059                 next_out = i;
3060                 started = STARTED_BOARD;
3061                 parse_pos = 0;
3062                 continue;
3063             }
3064
3065             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3066                 looking_at(buf, &i, "<b1> ")) {
3067                 if (oldi > next_out) {
3068                     SendToPlayer(&buf[next_out], oldi - next_out);
3069                 }
3070                 next_out = i;
3071                 started = STARTED_HOLDINGS;
3072                 parse_pos = 0;
3073                 continue;
3074             }
3075
3076             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077                 loggedOn = TRUE;
3078                 /* Header for a move list -- first line */
3079
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     switch (gameMode) {
3083                       case IcsIdle:
3084                       case BeginningOfGame:
3085                         /* User typed "moves" or "oldmoves" while we
3086                            were idle.  Pretend we asked for these
3087                            moves and soak them up so user can step
3088                            through them and/or save them.
3089                            */
3090                         Reset(FALSE, TRUE);
3091                         gameMode = IcsObserving;
3092                         ModeHighlight();
3093                         ics_gamenum = -1;
3094                         ics_getting_history = H_GOT_UNREQ_HEADER;
3095                         break;
3096                       case EditGame: /*?*/
3097                       case EditPosition: /*?*/
3098                         /* Should above feature work in these modes too? */
3099                         /* For now it doesn't */
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                       default:
3103                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3104                         break;
3105                     }
3106                     break;
3107                   case H_REQUESTED:
3108                     /* Is this the right one? */
3109                     if (gameInfo.white && gameInfo.black &&
3110                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3111                         strcmp(gameInfo.black, star_match[2]) == 0) {
3112                         /* All is well */
3113                         ics_getting_history = H_GOT_REQ_HEADER;
3114                     }
3115                     break;
3116                   case H_GOT_REQ_HEADER:
3117                   case H_GOT_UNREQ_HEADER:
3118                   case H_GOT_UNWANTED_HEADER:
3119                   case H_GETTING_MOVES:
3120                     /* Should not happen */
3121                     DisplayError(_("Error gathering move list: two headers"), 0);
3122                     ics_getting_history = H_FALSE;
3123                     break;
3124                 }
3125
3126                 /* Save player ratings into gameInfo if needed */
3127                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3128                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3129                     (gameInfo.whiteRating == -1 ||
3130                      gameInfo.blackRating == -1)) {
3131
3132                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3133                     gameInfo.blackRating = string_to_rating(star_match[3]);
3134                     if (appData.debugMode)
3135                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3136                               gameInfo.whiteRating, gameInfo.blackRating);
3137                 }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i,
3142               "* * match, initial time: * minute*, increment: * second")) {
3143                 /* Header for a move list -- second line */
3144                 /* Initial board will follow if this is a wild game */
3145                 if (gameInfo.event != NULL) free(gameInfo.event);
3146                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3147                 gameInfo.event = StrSave(str);
3148                 /* [HGM] we switched variant. Translate boards if needed. */
3149                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "Move  ")) {
3154                 /* Beginning of a move list */
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     /* Normally should not happen */
3158                     /* Maybe user hit reset while we were parsing */
3159                     break;
3160                   case H_REQUESTED:
3161                     /* Happens if we are ignoring a move list that is not
3162                      * the one we just requested.  Common if the user
3163                      * tries to observe two games without turning off
3164                      * getMoveList */
3165                     break;
3166                   case H_GETTING_MOVES:
3167                     /* Should not happen */
3168                     DisplayError(_("Error gathering move list: nested"), 0);
3169                     ics_getting_history = H_FALSE;
3170                     break;
3171                   case H_GOT_REQ_HEADER:
3172                     ics_getting_history = H_GETTING_MOVES;
3173                     started = STARTED_MOVES;
3174                     parse_pos = 0;
3175                     if (oldi > next_out) {
3176                         SendToPlayer(&buf[next_out], oldi - next_out);
3177                     }
3178                     break;
3179                   case H_GOT_UNREQ_HEADER:
3180                     ics_getting_history = H_GETTING_MOVES;
3181                     started = STARTED_MOVES_NOHIDE;
3182                     parse_pos = 0;
3183                     break;
3184                   case H_GOT_UNWANTED_HEADER:
3185                     ics_getting_history = H_FALSE;
3186                     break;
3187                 }
3188                 continue;
3189             }                           
3190             
3191             if (looking_at(buf, &i, "% ") ||
3192                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3193                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3194                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                 }
3199                 if(suppressKibitz) next_out = i;
3200                 savingComment = FALSE;
3201                 suppressKibitz = 0;
3202                 switch (started) {
3203                   case STARTED_MOVES:
3204                   case STARTED_MOVES_NOHIDE:
3205                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3206                     parse[parse_pos + i - oldi] = NULLCHAR;
3207                     ParseGameHistory(parse);
3208 #if ZIPPY
3209                     if (appData.zippyPlay && first.initDone) {
3210                         FeedMovesToProgram(&first, forwardMostMove);
3211                         if (gameMode == IcsPlayingWhite) {
3212                             if (WhiteOnMove(forwardMostMove)) {
3213                                 if (first.sendTime) {
3214                                   if (first.useColors) {
3215                                     SendToProgram("black\n", &first); 
3216                                   }
3217                                   SendTimeRemaining(&first, TRUE);
3218                                 }
3219                                 if (first.useColors) {
3220                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221                                 }
3222                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3223                                 first.maybeThinking = TRUE;
3224                             } else {
3225                                 if (first.usePlayother) {
3226                                   if (first.sendTime) {
3227                                     SendTimeRemaining(&first, TRUE);
3228                                   }
3229                                   SendToProgram("playother\n", &first);
3230                                   firstMove = FALSE;
3231                                 } else {
3232                                   firstMove = TRUE;
3233                                 }
3234                             }
3235                         } else if (gameMode == IcsPlayingBlack) {
3236                             if (!WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("white\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, FALSE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("black\n", &first);
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, FALSE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         }                       
3260                     }
3261 #endif
3262                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3263                         /* Moves came from oldmoves or moves command
3264                            while we weren't doing anything else.
3265                            */
3266                         currentMove = forwardMostMove;
3267                         ClearHighlights();/*!!could figure this out*/
3268                         flipView = appData.flipView;
3269                         DrawPosition(TRUE, boards[currentMove]);
3270                         DisplayBothClocks();
3271                         sprintf(str, "%s vs. %s",
3272                                 gameInfo.white, gameInfo.black);
3273                         DisplayTitle(str);
3274                         gameMode = IcsIdle;
3275                     } else {
3276                         /* Moves were history of an active game */
3277                         if (gameInfo.resultDetails != NULL) {
3278                             free(gameInfo.resultDetails);
3279                             gameInfo.resultDetails = NULL;
3280                         }
3281                     }
3282                     HistorySet(parseList, backwardMostMove,
3283                                forwardMostMove, currentMove-1);
3284                     DisplayMove(currentMove - 1);
3285                     if (started == STARTED_MOVES) next_out = i;
3286                     started = STARTED_NONE;
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289
3290                   case STARTED_OBSERVE:
3291                     started = STARTED_NONE;
3292                     SendToICS(ics_prefix);
3293                     SendToICS("refresh\n");
3294                     break;
3295
3296                   default:
3297                     break;
3298                 }
3299                 if(bookHit) { // [HGM] book: simulate book reply
3300                     static char bookMove[MSG_SIZ]; // a bit generous?
3301
3302                     programStats.nodes = programStats.depth = programStats.time = 
3303                     programStats.score = programStats.got_only_move = 0;
3304                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305
3306                     strcpy(bookMove, "move ");
3307                     strcat(bookMove, bookHit);
3308                     HandleMachineMove(bookMove, &first);
3309                 }
3310                 continue;
3311             }
3312             
3313             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3314                  started == STARTED_HOLDINGS ||
3315                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3316                 /* Accumulate characters in move list or board */
3317                 parse[parse_pos++] = buf[i];
3318             }
3319             
3320             /* Start of game messages.  Mostly we detect start of game
3321                when the first board image arrives.  On some versions
3322                of the ICS, though, we need to do a "refresh" after starting
3323                to observe in order to get the current board right away. */
3324             if (looking_at(buf, &i, "Adding game * to observation list")) {
3325                 started = STARTED_OBSERVE;
3326                 continue;
3327             }
3328
3329             /* Handle auto-observe */
3330             if (appData.autoObserve &&
3331                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3332                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333                 char *player;
3334                 /* Choose the player that was highlighted, if any. */
3335                 if (star_match[0][0] == '\033' ||
3336                     star_match[1][0] != '\033') {
3337                     player = star_match[0];
3338                 } else {
3339                     player = star_match[2];
3340                 }
3341                 sprintf(str, "%sobserve %s\n",
3342                         ics_prefix, StripHighlightAndTitle(player));
3343                 SendToICS(str);
3344
3345                 /* Save ratings from notify string */
3346                 strcpy(player1Name, star_match[0]);
3347                 player1Rating = string_to_rating(star_match[1]);
3348                 strcpy(player2Name, star_match[2]);
3349                 player2Rating = string_to_rating(star_match[3]);
3350
3351                 if (appData.debugMode)
3352                   fprintf(debugFP, 
3353                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3354                           player1Name, player1Rating,
3355                           player2Name, player2Rating);
3356
3357                 continue;
3358             }
3359
3360             /* Deal with automatic examine mode after a game,
3361                and with IcsObserving -> IcsExamining transition */
3362             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3363                 looking_at(buf, &i, "has made you an examiner of game *")) {
3364
3365                 int gamenum = atoi(star_match[0]);
3366                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3367                     gamenum == ics_gamenum) {
3368                     /* We were already playing or observing this game;
3369                        no need to refetch history */
3370                     gameMode = IcsExamining;
3371                     if (pausing) {
3372                         pauseExamForwardMostMove = forwardMostMove;
3373                     } else if (currentMove < forwardMostMove) {
3374                         ForwardInner(forwardMostMove);
3375                     }
3376                 } else {
3377                     /* I don't think this case really can happen */
3378                     SendToICS(ics_prefix);
3379                     SendToICS("refresh\n");
3380                 }
3381                 continue;
3382             }    
3383             
3384             /* Error messages */
3385 //          if (ics_user_moved) {
3386             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3387                 if (looking_at(buf, &i, "Illegal move") ||
3388                     looking_at(buf, &i, "Not a legal move") ||
3389                     looking_at(buf, &i, "Your king is in check") ||
3390                     looking_at(buf, &i, "It isn't your turn") ||
3391                     looking_at(buf, &i, "It is not your move")) {
3392                     /* Illegal move */
3393                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3394                         currentMove = forwardMostMove-1;
3395                         DisplayMove(currentMove - 1); /* before DMError */
3396                         DrawPosition(FALSE, boards[currentMove]);
3397                         SwitchClocks(forwardMostMove-1); // [HGM] race
3398                         DisplayBothClocks();
3399                     }
3400                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3401                     ics_user_moved = 0;
3402                     continue;
3403                 }
3404             }
3405
3406             if (looking_at(buf, &i, "still have time") ||
3407                 looking_at(buf, &i, "not out of time") ||
3408                 looking_at(buf, &i, "either player is out of time") ||
3409                 looking_at(buf, &i, "has timeseal; checking")) {
3410                 /* We must have called his flag a little too soon */
3411                 whiteFlag = blackFlag = FALSE;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "added * seconds to") ||
3416                 looking_at(buf, &i, "seconds were added to")) {
3417                 /* Update the clocks */
3418                 SendToICS(ics_prefix);
3419                 SendToICS("refresh\n");
3420                 continue;
3421             }
3422
3423             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3424                 ics_clock_paused = TRUE;
3425                 StopClocks();
3426                 continue;
3427             }
3428
3429             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3430                 ics_clock_paused = FALSE;
3431                 StartClocks();
3432                 continue;
3433             }
3434
3435             /* Grab player ratings from the Creating: message.
3436                Note we have to check for the special case when
3437                the ICS inserts things like [white] or [black]. */
3438             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3439                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440                 /* star_matches:
3441                    0    player 1 name (not necessarily white)
3442                    1    player 1 rating
3443                    2    empty, white, or black (IGNORED)
3444                    3    player 2 name (not necessarily black)
3445                    4    player 2 rating
3446                    
3447                    The names/ratings are sorted out when the game
3448                    actually starts (below).
3449                 */
3450                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3451                 player1Rating = string_to_rating(star_match[1]);
3452                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3453                 player2Rating = string_to_rating(star_match[4]);
3454
3455                 if (appData.debugMode)
3456                   fprintf(debugFP, 
3457                           "Ratings from 'Creating:' %s %d, %s %d\n",
3458                           player1Name, player1Rating,
3459                           player2Name, player2Rating);
3460
3461                 continue;
3462             }
3463             
3464             /* Improved generic start/end-of-game messages */
3465             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3466                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3467                 /* If tkind == 0: */
3468                 /* star_match[0] is the game number */
3469                 /*           [1] is the white player's name */
3470                 /*           [2] is the black player's name */
3471                 /* For end-of-game: */
3472                 /*           [3] is the reason for the game end */
3473                 /*           [4] is a PGN end game-token, preceded by " " */
3474                 /* For start-of-game: */
3475                 /*           [3] begins with "Creating" or "Continuing" */
3476                 /*           [4] is " *" or empty (don't care). */
3477                 int gamenum = atoi(star_match[0]);
3478                 char *whitename, *blackname, *why, *endtoken;
3479                 ChessMove endtype = (ChessMove) 0;
3480
3481                 if (tkind == 0) {
3482                   whitename = star_match[1];
3483                   blackname = star_match[2];
3484                   why = star_match[3];
3485                   endtoken = star_match[4];
3486                 } else {
3487                   whitename = star_match[1];
3488                   blackname = star_match[3];
3489                   why = star_match[5];
3490                   endtoken = star_match[6];
3491                 }
3492
3493                 /* Game start messages */
3494                 if (strncmp(why, "Creating ", 9) == 0 ||
3495                     strncmp(why, "Continuing ", 11) == 0) {
3496                     gs_gamenum = gamenum;
3497                     strcpy(gs_kind, strchr(why, ' ') + 1);
3498                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 #if ZIPPY
3500                     if (appData.zippyPlay) {
3501                         ZippyGameStart(whitename, blackname);
3502                     }
3503 #endif /*ZIPPY*/
3504                     partnerBoardValid = FALSE; // [HGM] bughouse
3505                     continue;
3506                 }
3507
3508                 /* Game end messages */
3509                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3510                     ics_gamenum != gamenum) {
3511                     continue;
3512                 }
3513                 while (endtoken[0] == ' ') endtoken++;
3514                 switch (endtoken[0]) {
3515                   case '*':
3516                   default:
3517                     endtype = GameUnfinished;
3518                     break;
3519                   case '0':
3520                     endtype = BlackWins;
3521                     break;
3522                   case '1':
3523                     if (endtoken[1] == '/')
3524                       endtype = GameIsDrawn;
3525                     else
3526                       endtype = WhiteWins;
3527                     break;
3528                 }
3529                 GameEnds(endtype, why, GE_ICS);
3530 #if ZIPPY
3531                 if (appData.zippyPlay && first.initDone) {
3532                     ZippyGameEnd(endtype, why);
3533                     if (first.pr == NULL) {
3534                       /* Start the next process early so that we'll
3535                          be ready for the next challenge */
3536                       StartChessProgram(&first);
3537                     }
3538                     /* Send "new" early, in case this command takes
3539                        a long time to finish, so that we'll be ready
3540                        for the next challenge. */
3541                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3542                     Reset(TRUE, TRUE);
3543                 }
3544 #endif /*ZIPPY*/
3545                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "Removing game * from observation") ||
3550                 looking_at(buf, &i, "no longer observing game *") ||
3551                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3552                 if (gameMode == IcsObserving &&
3553                     atoi(star_match[0]) == ics_gamenum)
3554                   {
3555                       /* icsEngineAnalyze */
3556                       if (appData.icsEngineAnalyze) {
3557                             ExitAnalyzeMode();
3558                             ModeHighlight();
3559                       }
3560                       StopClocks();
3561                       gameMode = IcsIdle;
3562                       ics_gamenum = -1;
3563                       ics_user_moved = FALSE;
3564                   }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "no longer examining game *")) {
3569                 if (gameMode == IcsExamining &&
3570                     atoi(star_match[0]) == ics_gamenum)
3571                   {
3572                       gameMode = IcsIdle;
3573                       ics_gamenum = -1;
3574                       ics_user_moved = FALSE;
3575                   }
3576                 continue;
3577             }
3578
3579             /* Advance leftover_start past any newlines we find,
3580                so only partial lines can get reparsed */
3581             if (looking_at(buf, &i, "\n")) {
3582                 prevColor = curColor;
3583                 if (curColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(ColorNormal, FALSE);
3589                     curColor = ColorNormal;
3590                 }
3591                 if (started == STARTED_BOARD) {
3592                     started = STARTED_NONE;
3593                     parse[parse_pos] = NULLCHAR;
3594                     ParseBoard12(parse);
3595                     ics_user_moved = 0;
3596
3597                     /* Send premove here */
3598                     if (appData.premove) {
3599                       char str[MSG_SIZ];
3600                       if (currentMove == 0 &&
3601                           gameMode == IcsPlayingWhite &&
3602                           appData.premoveWhite) {
3603                         sprintf(str, "%s\n", appData.premoveWhiteText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (currentMove == 1 &&
3608                                  gameMode == IcsPlayingBlack &&
3609                                  appData.premoveBlack) {
3610                         sprintf(str, "%s\n", appData.premoveBlackText);
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                         SendToICS(str);
3614                       } else if (gotPremove) {
3615                         gotPremove = 0;
3616                         ClearPremoveHighlights();
3617                         if (appData.debugMode)
3618                           fprintf(debugFP, "Sending premove:\n");
3619                           UserMoveEvent(premoveFromX, premoveFromY, 
3620                                         premoveToX, premoveToY, 
3621                                         premovePromoChar);
3622                       }
3623                     }
3624
3625                     /* Usually suppress following prompt */
3626                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3627                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3628                         if (looking_at(buf, &i, "*% ")) {
3629                             savingComment = FALSE;
3630                             suppressKibitz = 0;
3631                         }
3632                     }
3633                     next_out = i;
3634                 } else if (started == STARTED_HOLDINGS) {
3635                     int gamenum;
3636                     char new_piece[MSG_SIZ];
3637                     started = STARTED_NONE;
3638                     parse[parse_pos] = NULLCHAR;
3639                     if (appData.debugMode)
3640                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3641                                                         parse, currentMove);
3642                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3643                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3644                         if (gameInfo.variant == VariantNormal) {
3645                           /* [HGM] We seem to switch variant during a game!
3646                            * Presumably no holdings were displayed, so we have
3647                            * to move the position two files to the right to
3648                            * create room for them!
3649                            */
3650                           VariantClass newVariant;
3651                           switch(gameInfo.boardWidth) { // base guess on board width
3652                                 case 9:  newVariant = VariantShogi; break;
3653                                 case 10: newVariant = VariantGreat; break;
3654                                 default: newVariant = VariantCrazyhouse; break;
3655                           }
3656                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3657                           /* Get a move list just to see the header, which
3658                              will tell us whether this is really bug or zh */
3659                           if (ics_getting_history == H_FALSE) {
3660                             ics_getting_history = H_REQUESTED;
3661                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662                             SendToICS(str);
3663                           }
3664                         }
3665                         new_piece[0] = NULLCHAR;
3666                         sscanf(parse, "game %d white [%s black [%s <- %s",
3667                                &gamenum, white_holding, black_holding,
3668                                new_piece);
3669                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3670                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3671                         /* [HGM] copy holdings to board holdings area */
3672                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3673                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3674                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 #if ZIPPY
3676                         if (appData.zippyPlay && first.initDone) {
3677                             ZippyHoldings(white_holding, black_holding,
3678                                           new_piece);
3679                         }
3680 #endif /*ZIPPY*/
3681                         if (tinyLayout || smallLayout) {
3682                             char wh[16], bh[16];
3683                             PackHolding(wh, white_holding);
3684                             PackHolding(bh, black_holding);
3685                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3686                                     gameInfo.white, gameInfo.black);
3687                         } else {
3688                             sprintf(str, "%s [%s] vs. %s [%s]",
3689                                     gameInfo.white, white_holding,
3690                                     gameInfo.black, black_holding);
3691                         }
3692                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3693                         DrawPosition(FALSE, boards[currentMove]);
3694                         DisplayTitle(str);
3695                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3696                         sscanf(parse, "game %d white [%s black [%s <- %s",
3697                                &gamenum, white_holding, black_holding,
3698                                new_piece);
3699                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3700                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3701                         /* [HGM] copy holdings to partner-board holdings area */
3702                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3703                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3704                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3705                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3706                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707                       }
3708                     }
3709                     /* Suppress following prompt */
3710                     if (looking_at(buf, &i, "*% ")) {
3711                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3712                         savingComment = FALSE;
3713                         suppressKibitz = 0;
3714                     }
3715                     next_out = i;
3716                 }
3717                 continue;
3718             }
3719
3720             i++;                /* skip unparsed character and loop back */
3721         }
3722         
3723         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3724 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3725 //          SendToPlayer(&buf[next_out], i - next_out);
3726             started != STARTED_HOLDINGS && leftover_start > next_out) {
3727             SendToPlayer(&buf[next_out], leftover_start - next_out);
3728             next_out = i;
3729         }
3730         
3731         leftover_len = buf_len - leftover_start;
3732         /* if buffer ends with something we couldn't parse,
3733            reparse it after appending the next read */
3734         
3735     } else if (count == 0) {
3736         RemoveInputSource(isr);
3737         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738     } else {
3739         DisplayFatalError(_("Error reading from ICS"), error, 1);
3740     }
3741 }
3742
3743
3744 /* Board style 12 looks like this:
3745    
3746    <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
3747    
3748  * The "<12> " is stripped before it gets to this routine.  The two
3749  * trailing 0's (flip state and clock ticking) are later addition, and
3750  * some chess servers may not have them, or may have only the first.
3751  * Additional trailing fields may be added in the future.  
3752  */
3753
3754 #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"
3755
3756 #define RELATION_OBSERVING_PLAYED    0
3757 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3758 #define RELATION_PLAYING_MYMOVE      1
3759 #define RELATION_PLAYING_NOTMYMOVE  -1
3760 #define RELATION_EXAMINING           2
3761 #define RELATION_ISOLATED_BOARD     -3
3762 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3763
3764 void
3765 ParseBoard12(string)
3766      char *string;
3767
3768     GameMode newGameMode;
3769     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3770     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3771     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3772     char to_play, board_chars[200];
3773     char move_str[500], str[500], elapsed_time[500];
3774     char black[32], white[32];
3775     Board board;
3776     int prevMove = currentMove;
3777     int ticking = 2;
3778     ChessMove moveType;
3779     int fromX, fromY, toX, toY;
3780     char promoChar;
3781     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3782     char *bookHit = NULL; // [HGM] book
3783     Boolean weird = FALSE, reqFlag = FALSE;
3784
3785     fromX = fromY = toX = toY = -1;
3786     
3787     newGame = FALSE;
3788
3789     if (appData.debugMode)
3790       fprintf(debugFP, _("Parsing board: %s\n"), string);
3791
3792     move_str[0] = NULLCHAR;
3793     elapsed_time[0] = NULLCHAR;
3794     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795         int  i = 0, j;
3796         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3797             if(string[i] == ' ') { ranks++; files = 0; }
3798             else files++;
3799             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800             i++;
3801         }
3802         for(j = 0; j <i; j++) board_chars[j] = string[j];
3803         board_chars[i] = '\0';
3804         string += i + 1;
3805     }
3806     n = sscanf(string, PATTERN, &to_play, &double_push,
3807                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3808                &gamenum, white, black, &relation, &basetime, &increment,
3809                &white_stren, &black_stren, &white_time, &black_time,
3810                &moveNum, str, elapsed_time, move_str, &ics_flip,
3811                &ticking);
3812
3813     if (n < 21) {
3814         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3815         DisplayError(str, 0);
3816         return;
3817     }
3818
3819     /* Convert the move number to internal form */
3820     moveNum = (moveNum - 1) * 2;
3821     if (to_play == 'B') moveNum++;
3822     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3823       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3824                         0, 1);
3825       return;
3826     }
3827     
3828     switch (relation) {
3829       case RELATION_OBSERVING_PLAYED:
3830       case RELATION_OBSERVING_STATIC:
3831         if (gamenum == -1) {
3832             /* Old ICC buglet */
3833             relation = RELATION_OBSERVING_STATIC;
3834         }
3835         newGameMode = IcsObserving;
3836         break;
3837       case RELATION_PLAYING_MYMOVE:
3838       case RELATION_PLAYING_NOTMYMOVE:
3839         newGameMode =
3840           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3841             IcsPlayingWhite : IcsPlayingBlack;
3842         break;
3843       case RELATION_EXAMINING:
3844         newGameMode = IcsExamining;
3845         break;
3846       case RELATION_ISOLATED_BOARD:
3847       default:
3848         /* Just display this board.  If user was doing something else,
3849            we will forget about it until the next board comes. */ 
3850         newGameMode = IcsIdle;
3851         break;
3852       case RELATION_STARTING_POSITION:
3853         newGameMode = gameMode;
3854         break;
3855     }
3856     
3857     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3858          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3859       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860       char *toSqr;
3861       for (k = 0; k < ranks; k++) {
3862         for (j = 0; j < files; j++)
3863           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3864         if(gameInfo.holdingsWidth > 1) {
3865              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3866              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3867         }
3868       }
3869       CopyBoard(partnerBoard, board);
3870       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3871         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3872         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3873       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3874       if(toSqr = strchr(str, '-')) {
3875         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3876         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3877       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3878       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3879       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3880       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3881       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3882       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3883                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3884       DisplayMessage(partnerStatus, "");
3885         partnerBoardValid = TRUE;
3886       return;
3887     }
3888
3889     /* Modify behavior for initial board display on move listing
3890        of wild games.
3891        */
3892     switch (ics_getting_history) {
3893       case H_FALSE:
3894       case H_REQUESTED:
3895         break;
3896       case H_GOT_REQ_HEADER:
3897       case H_GOT_UNREQ_HEADER:
3898         /* This is the initial position of the current game */
3899         gamenum = ics_gamenum;
3900         moveNum = 0;            /* old ICS bug workaround */
3901         if (to_play == 'B') {
3902           startedFromSetupPosition = TRUE;
3903           blackPlaysFirst = TRUE;
3904           moveNum = 1;
3905           if (forwardMostMove == 0) forwardMostMove = 1;
3906           if (backwardMostMove == 0) backwardMostMove = 1;
3907           if (currentMove == 0) currentMove = 1;
3908         }
3909         newGameMode = gameMode;
3910         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911         break;
3912       case H_GOT_UNWANTED_HEADER:
3913         /* This is an initial board that we don't want */
3914         return;
3915       case H_GETTING_MOVES:
3916         /* Should not happen */
3917         DisplayError(_("Error gathering move list: extra board"), 0);
3918         ics_getting_history = H_FALSE;
3919         return;
3920     }
3921
3922    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3923                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3924      /* [HGM] We seem to have switched variant unexpectedly
3925       * Try to guess new variant from board size
3926       */
3927           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3928           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3929           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3930           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3931           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3932           if(!weird) newVariant = VariantNormal;
3933           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3934           /* Get a move list just to see the header, which
3935              will tell us whether this is really bug or zh */
3936           if (ics_getting_history == H_FALSE) {
3937             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940           }
3941     }
3942     
3943     /* Take action if this is the first board of a new game, or of a
3944        different game than is currently being displayed.  */
3945     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3946         relation == RELATION_ISOLATED_BOARD) {
3947         
3948         /* Forget the old game and get the history (if any) of the new one */
3949         if (gameMode != BeginningOfGame) {
3950           Reset(TRUE, TRUE);
3951         }
3952         newGame = TRUE;
3953         if (appData.autoRaiseBoard) BoardToTop();
3954         prevMove = -3;
3955         if (gamenum == -1) {
3956             newGameMode = IcsIdle;
3957         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3958                    appData.getMoveList && !reqFlag) {
3959             /* Need to get game history */
3960             ics_getting_history = H_REQUESTED;
3961             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3962             SendToICS(str);
3963         }
3964         
3965         /* Initially flip the board to have black on the bottom if playing
3966            black or if the ICS flip flag is set, but let the user change
3967            it with the Flip View button. */
3968         flipView = appData.autoFlipView ? 
3969           (newGameMode == IcsPlayingBlack) || ics_flip :
3970           appData.flipView;
3971         
3972         /* Done with values from previous mode; copy in new ones */
3973         gameMode = newGameMode;
3974         ModeHighlight();
3975         ics_gamenum = gamenum;
3976         if (gamenum == gs_gamenum) {
3977             int klen = strlen(gs_kind);
3978             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3979             sprintf(str, "ICS %s", gs_kind);
3980             gameInfo.event = StrSave(str);
3981         } else {
3982             gameInfo.event = StrSave("ICS game");
3983         }
3984         gameInfo.site = StrSave(appData.icsHost);
3985         gameInfo.date = PGNDate();
3986         gameInfo.round = StrSave("-");
3987         gameInfo.white = StrSave(white);
3988         gameInfo.black = StrSave(black);
3989         timeControl = basetime * 60 * 1000;
3990         timeControl_2 = 0;
3991         timeIncrement = increment * 1000;
3992         movesPerSession = 0;
3993         gameInfo.timeControl = TimeControlTagValue();
3994         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3997     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3998     setbuf(debugFP, NULL);
3999   }
4000
4001         gameInfo.outOfBook = NULL;
4002         
4003         /* Do we have the ratings? */
4004         if (strcmp(player1Name, white) == 0 &&
4005             strcmp(player2Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player1Rating, player2Rating);
4009             gameInfo.whiteRating = player1Rating;
4010             gameInfo.blackRating = player2Rating;
4011         } else if (strcmp(player2Name, white) == 0 &&
4012                    strcmp(player1Name, black) == 0) {
4013             if (appData.debugMode)
4014               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4015                       player2Rating, player1Rating);
4016             gameInfo.whiteRating = player2Rating;
4017             gameInfo.blackRating = player1Rating;
4018         }
4019         player1Name[0] = player2Name[0] = NULLCHAR;
4020
4021         /* Silence shouts if requested */
4022         if (appData.quietPlay &&
4023             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4024             SendToICS(ics_prefix);
4025             SendToICS("set shout 0\n");
4026         }
4027     }
4028     
4029     /* Deal with midgame name changes */
4030     if (!newGame) {
4031         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4032             if (gameInfo.white) free(gameInfo.white);
4033             gameInfo.white = StrSave(white);
4034         }
4035         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4036             if (gameInfo.black) free(gameInfo.black);
4037             gameInfo.black = StrSave(black);
4038         }
4039     }
4040     
4041     /* Throw away game result if anything actually changes in examine mode */
4042     if (gameMode == IcsExamining && !newGame) {
4043         gameInfo.result = GameUnfinished;
4044         if (gameInfo.resultDetails != NULL) {
4045             free(gameInfo.resultDetails);
4046             gameInfo.resultDetails = NULL;
4047         }
4048     }
4049     
4050     /* In pausing && IcsExamining mode, we ignore boards coming
4051        in if they are in a different variation than we are. */
4052     if (pauseExamInvalid) return;
4053     if (pausing && gameMode == IcsExamining) {
4054         if (moveNum <= pauseExamForwardMostMove) {
4055             pauseExamInvalid = TRUE;
4056             forwardMostMove = pauseExamForwardMostMove;
4057             return;
4058         }
4059     }
4060     
4061   if (appData.debugMode) {
4062     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063   }
4064     /* Parse the board */
4065     for (k = 0; k < ranks; k++) {
4066       for (j = 0; j < files; j++)
4067         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4068       if(gameInfo.holdingsWidth > 1) {
4069            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4070            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4071       }
4072     }
4073     CopyBoard(boards[moveNum], board);
4074     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075     if (moveNum == 0) {
4076         startedFromSetupPosition =
4077           !CompareBoards(board, initialPosition);
4078         if(startedFromSetupPosition)
4079             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4080     }
4081
4082     /* [HGM] Set castling rights. Take the outermost Rooks,
4083        to make it also work for FRC opening positions. Note that board12
4084        is really defective for later FRC positions, as it has no way to
4085        indicate which Rook can castle if they are on the same side of King.
4086        For the initial position we grant rights to the outermost Rooks,
4087        and remember thos rights, and we then copy them on positions
4088        later in an FRC game. This means WB might not recognize castlings with
4089        Rooks that have moved back to their original position as illegal,
4090        but in ICS mode that is not its job anyway.
4091     */
4092     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4093     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094
4095         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4096             if(board[0][i] == WhiteRook) j = i;
4097         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4098         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4099             if(board[0][i] == WhiteRook) j = i;
4100         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4101         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4102             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4103         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4104         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4105             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4106         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107
4108         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[BOARD_HEIGHT-1][k] == bKing)
4113                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4114         if(gameInfo.variant == VariantTwoKings) {
4115             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4116             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4117             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4118         }
4119     } else { int r;
4120         r = boards[moveNum][CASTLING][0] = initialRights[0];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4122         r = boards[moveNum][CASTLING][1] = initialRights[1];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4124         r = boards[moveNum][CASTLING][3] = initialRights[3];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4126         r = boards[moveNum][CASTLING][4] = initialRights[4];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4128         /* wildcastle kludge: always assume King has rights */
4129         r = boards[moveNum][CASTLING][2] = initialRights[2];
4130         r = boards[moveNum][CASTLING][5] = initialRights[5];
4131     }
4132     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4133     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4134
4135     
4136     if (ics_getting_history == H_GOT_REQ_HEADER ||
4137         ics_getting_history == H_GOT_UNREQ_HEADER) {
4138         /* This was an initial position from a move list, not
4139            the current position */
4140         return;
4141     }
4142     
4143     /* Update currentMove and known move number limits */
4144     newMove = newGame || moveNum > forwardMostMove;
4145
4146     if (newGame) {
4147         forwardMostMove = backwardMostMove = currentMove = moveNum;
4148         if (gameMode == IcsExamining && moveNum == 0) {
4149           /* Workaround for ICS limitation: we are not told the wild
4150              type when starting to examine a game.  But if we ask for
4151              the move list, the move list header will tell us */
4152             ics_getting_history = H_REQUESTED;
4153             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4154             SendToICS(str);
4155         }
4156     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4157                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 #if ZIPPY
4159         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4160         /* [HGM] applied this also to an engine that is silently watching        */
4161         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4162             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4163             gameInfo.variant == currentlyInitializedVariant) {
4164           takeback = forwardMostMove - moveNum;
4165           for (i = 0; i < takeback; i++) {
4166             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4167             SendToProgram("undo\n", &first);
4168           }
4169         }
4170 #endif
4171
4172         forwardMostMove = moveNum;
4173         if (!pausing || currentMove > forwardMostMove)
4174           currentMove = forwardMostMove;
4175     } else {
4176         /* New part of history that is not contiguous with old part */ 
4177         if (pausing && gameMode == IcsExamining) {
4178             pauseExamInvalid = TRUE;
4179             forwardMostMove = pauseExamForwardMostMove;
4180             return;
4181         }
4182         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 #if ZIPPY
4184             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4185                 // [HGM] when we will receive the move list we now request, it will be
4186                 // fed to the engine from the first move on. So if the engine is not
4187                 // in the initial position now, bring it there.
4188                 InitChessProgram(&first, 0);
4189             }
4190 #endif
4191             ics_getting_history = H_REQUESTED;
4192             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4193             SendToICS(str);
4194         }
4195         forwardMostMove = backwardMostMove = currentMove = moveNum;
4196     }
4197     
4198     /* Update the clocks */
4199     if (strchr(elapsed_time, '.')) {
4200       /* Time is in ms */
4201       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4202       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203     } else {
4204       /* Time is in seconds */
4205       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4206       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4207     }
4208       
4209
4210 #if ZIPPY
4211     if (appData.zippyPlay && newGame &&
4212         gameMode != IcsObserving && gameMode != IcsIdle &&
4213         gameMode != IcsExamining)
4214       ZippyFirstBoard(moveNum, basetime, increment);
4215 #endif
4216     
4217     /* Put the move on the move list, first converting
4218        to canonical algebraic form. */
4219     if (moveNum > 0) {
4220   if (appData.debugMode) {
4221     if (appData.debugMode) { int f = forwardMostMove;
4222         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4223                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4224                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225     }
4226     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4227     fprintf(debugFP, "moveNum = %d\n", moveNum);
4228     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4229     setbuf(debugFP, NULL);
4230   }
4231         if (moveNum <= backwardMostMove) {
4232             /* We don't know what the board looked like before
4233                this move.  Punt. */
4234             strcpy(parseList[moveNum - 1], move_str);
4235             strcat(parseList[moveNum - 1], " ");
4236             strcat(parseList[moveNum - 1], elapsed_time);
4237             moveList[moveNum - 1][0] = NULLCHAR;
4238         } else if (strcmp(move_str, "none") == 0) {
4239             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4240             /* Again, we don't know what the board looked like;
4241                this is really the start of the game. */
4242             parseList[moveNum - 1][0] = NULLCHAR;
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             backwardMostMove = moveNum;
4245             startedFromSetupPosition = TRUE;
4246             fromX = fromY = toX = toY = -1;
4247         } else {
4248           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4249           //                 So we parse the long-algebraic move string in stead of the SAN move
4250           int valid; char buf[MSG_SIZ], *prom;
4251
4252           // str looks something like "Q/a1-a2"; kill the slash
4253           if(str[1] == '/') 
4254                 sprintf(buf, "%c%s", str[0], str+2);
4255           else  strcpy(buf, str); // might be castling
4256           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4257                 strcat(buf, prom); // long move lacks promo specification!
4258           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4259                 if(appData.debugMode) 
4260                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4261                 strcpy(move_str, buf);
4262           }
4263           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar)
4265                || ParseOneMove(buf, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar);
4267           // end of long SAN patch
4268           if (valid) {
4269             (void) CoordsToAlgebraic(boards[moveNum - 1],
4270                                      PosFlags(moveNum - 1),
4271                                      fromY, fromX, toY, toX, promoChar,
4272                                      parseList[moveNum-1]);
4273             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4274               case MT_NONE:
4275               case MT_STALEMATE:
4276               default:
4277                 break;
4278               case MT_CHECK:
4279                 if(gameInfo.variant != VariantShogi)
4280                     strcat(parseList[moveNum - 1], "+");
4281                 break;
4282               case MT_CHECKMATE:
4283               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4284                 strcat(parseList[moveNum - 1], "#");
4285                 break;
4286             }
4287             strcat(parseList[moveNum - 1], " ");
4288             strcat(parseList[moveNum - 1], elapsed_time);
4289             /* currentMoveString is set as a side-effect of ParseOneMove */
4290             strcpy(moveList[moveNum - 1], currentMoveString);
4291             strcat(moveList[moveNum - 1], "\n");
4292           } else {
4293             /* Move from ICS was illegal!?  Punt. */
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4296     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297   }
4298             strcpy(parseList[moveNum - 1], move_str);
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             moveList[moveNum - 1][0] = NULLCHAR;
4302             fromX = fromY = toX = toY = -1;
4303           }
4304         }
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4307     setbuf(debugFP, NULL);
4308   }
4309
4310 #if ZIPPY
4311         /* Send move to chess program (BEFORE animating it). */
4312         if (appData.zippyPlay && !newGame && newMove && 
4313            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314
4315             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4316                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4317                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4318                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319                             move_str);
4320                     DisplayError(str, 0);
4321                 } else {
4322                     if (first.sendTime) {
4323                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324                     }
4325                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4326                     if (firstMove && !bookHit) {
4327                         firstMove = FALSE;
4328                         if (first.useColors) {
4329                           SendToProgram(gameMode == IcsPlayingWhite ?
4330                                         "white\ngo\n" :
4331                                         "black\ngo\n", &first);
4332                         } else {
4333                           SendToProgram("go\n", &first);
4334                         }
4335                         first.maybeThinking = TRUE;
4336                     }
4337                 }
4338             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4339               if (moveList[moveNum - 1][0] == NULLCHAR) {
4340                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4341                 DisplayError(str, 0);
4342               } else {
4343                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4344                 SendMoveToProgram(moveNum - 1, &first);
4345               }
4346             }
4347         }
4348 #endif
4349     }
4350
4351     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4352         /* If move comes from a remote source, animate it.  If it
4353            isn't remote, it will have already been animated. */
4354         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4355             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356         }
4357         if (!pausing && appData.highlightLastMove) {
4358             SetHighlights(fromX, fromY, toX, toY);
4359         }
4360     }
4361     
4362     /* Start the clocks */
4363     whiteFlag = blackFlag = FALSE;
4364     appData.clockMode = !(basetime == 0 && increment == 0);
4365     if (ticking == 0) {
4366       ics_clock_paused = TRUE;
4367       StopClocks();
4368     } else if (ticking == 1) {
4369       ics_clock_paused = FALSE;
4370     }
4371     if (gameMode == IcsIdle ||
4372         relation == RELATION_OBSERVING_STATIC ||
4373         relation == RELATION_EXAMINING ||
4374         ics_clock_paused)
4375       DisplayBothClocks();
4376     else
4377       StartClocks();
4378     
4379     /* Display opponents and material strengths */
4380     if (gameInfo.variant != VariantBughouse &&
4381         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4382         if (tinyLayout || smallLayout) {
4383             if(gameInfo.variant == VariantNormal)
4384                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment);
4387             else
4388                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4389                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4390                     basetime, increment, (int) gameInfo.variant);
4391         } else {
4392             if(gameInfo.variant == VariantNormal)
4393                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4394                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4395                     basetime, increment);
4396             else
4397                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4398                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4399                     basetime, increment, VariantName(gameInfo.variant));
4400         }
4401         DisplayTitle(str);
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4404   }
4405     }
4406
4407
4408     /* Display the board */
4409     if (!pausing && !appData.noGUI) {
4410       
4411       if (appData.premove)
4412           if (!gotPremove || 
4413              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4414              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4415               ClearPremoveHighlights();
4416
4417       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4418         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4419       DrawPosition(j, boards[currentMove]);
4420
4421       DisplayMove(moveNum - 1);
4422       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4423             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4424               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4425         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4426       }
4427     }
4428
4429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 #if ZIPPY
4431     if(bookHit) { // [HGM] book: simulate book reply
4432         static char bookMove[MSG_SIZ]; // a bit generous?
4433
4434         programStats.nodes = programStats.depth = programStats.time = 
4435         programStats.score = programStats.got_only_move = 0;
4436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437
4438         strcpy(bookMove, "move ");
4439         strcat(bookMove, bookHit);
4440         HandleMachineMove(bookMove, &first);
4441     }
4442 #endif
4443 }
4444
4445 void
4446 GetMoveListEvent()
4447 {
4448     char buf[MSG_SIZ];
4449     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4450         ics_getting_history = H_REQUESTED;
4451         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4452         SendToICS(buf);
4453     }
4454 }
4455
4456 void
4457 AnalysisPeriodicEvent(force)
4458      int force;
4459 {
4460     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4461          && !force) || !appData.periodicUpdates)
4462       return;
4463
4464     /* Send . command to Crafty to collect stats */
4465     SendToProgram(".\n", &first);
4466
4467     /* Don't send another until we get a response (this makes
4468        us stop sending to old Crafty's which don't understand
4469        the "." command (sending illegal cmds resets node count & time,
4470        which looks bad)) */
4471     programStats.ok_to_send = 0;
4472 }
4473
4474 void ics_update_width(new_width)
4475         int new_width;
4476 {
4477         ics_printf("set width %d\n", new_width);
4478 }
4479
4480 void
4481 SendMoveToProgram(moveNum, cps)
4482      int moveNum;
4483      ChessProgramState *cps;
4484 {
4485     char buf[MSG_SIZ];
4486
4487     if (cps->useUsermove) {
4488       SendToProgram("usermove ", cps);
4489     }
4490     if (cps->useSAN) {
4491       char *space;
4492       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4493         int len = space - parseList[moveNum];
4494         memcpy(buf, parseList[moveNum], len);
4495         buf[len++] = '\n';
4496         buf[len] = NULLCHAR;
4497       } else {
4498         sprintf(buf, "%s\n", parseList[moveNum]);
4499       }
4500       SendToProgram(buf, cps);
4501     } else {
4502       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4503         AlphaRank(moveList[moveNum], 4);
4504         SendToProgram(moveList[moveNum], cps);
4505         AlphaRank(moveList[moveNum], 4); // and back
4506       } else
4507       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4508        * the engine. It would be nice to have a better way to identify castle 
4509        * moves here. */
4510       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4511                                                                          && cps->useOOCastle) {
4512         int fromX = moveList[moveNum][0] - AAA; 
4513         int fromY = moveList[moveNum][1] - ONE;
4514         int toX = moveList[moveNum][2] - AAA; 
4515         int toY = moveList[moveNum][3] - ONE;
4516         if((boards[moveNum][fromY][fromX] == WhiteKing 
4517             && boards[moveNum][toY][toX] == WhiteRook)
4518            || (boards[moveNum][fromY][fromX] == BlackKing 
4519                && boards[moveNum][toY][toX] == BlackRook)) {
4520           if(toX > fromX) SendToProgram("O-O\n", cps);
4521           else SendToProgram("O-O-O\n", cps);
4522         }
4523         else SendToProgram(moveList[moveNum], cps);
4524       }
4525       else SendToProgram(moveList[moveNum], cps);
4526       /* End of additions by Tord */
4527     }
4528
4529     /* [HGM] setting up the opening has brought engine in force mode! */
4530     /*       Send 'go' if we are in a mode where machine should play. */
4531     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4532         (gameMode == TwoMachinesPlay   ||
4533 #if ZIPPY
4534          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4535 #endif
4536          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4537         SendToProgram("go\n", cps);
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "(extra)\n");
4540   }
4541     }
4542     setboardSpoiledMachineBlack = 0;
4543 }
4544
4545 void
4546 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4547      ChessMove moveType;
4548      int fromX, fromY, toX, toY;
4549 {
4550     char user_move[MSG_SIZ];
4551
4552     switch (moveType) {
4553       default:
4554         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555                 (int)moveType, fromX, fromY, toX, toY);
4556         DisplayError(user_move + strlen("say "), 0);
4557         break;
4558       case WhiteKingSideCastle:
4559       case BlackKingSideCastle:
4560       case WhiteQueenSideCastleWild:
4561       case BlackQueenSideCastleWild:
4562       /* PUSH Fabien */
4563       case WhiteHSideCastleFR:
4564       case BlackHSideCastleFR:
4565       /* POP Fabien */
4566         sprintf(user_move, "o-o\n");
4567         break;
4568       case WhiteQueenSideCastle:
4569       case BlackQueenSideCastle:
4570       case WhiteKingSideCastleWild:
4571       case BlackKingSideCastleWild:
4572       /* PUSH Fabien */
4573       case WhiteASideCastleFR:
4574       case BlackASideCastleFR:
4575       /* POP Fabien */
4576         sprintf(user_move, "o-o-o\n");
4577         break;
4578       case WhiteNonPromotion:
4579       case BlackNonPromotion:
4580         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4581         break;
4582       case WhitePromotionQueen:
4583       case BlackPromotionQueen:
4584       case WhitePromotionRook:
4585       case BlackPromotionRook:
4586       case WhitePromotionBishop:
4587       case BlackPromotionBishop:
4588       case WhitePromotionKnight:
4589       case BlackPromotionKnight:
4590       case WhitePromotionKing:
4591       case BlackPromotionKing:
4592       case WhitePromotionChancellor:
4593       case BlackPromotionChancellor:
4594       case WhitePromotionArchbishop:
4595       case BlackPromotionArchbishop:
4596         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4597             sprintf(user_move, "%c%c%c%c=%c\n",
4598                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4599                 PieceToChar(WhiteFerz));
4600         else if(gameInfo.variant == VariantGreat)
4601             sprintf(user_move, "%c%c%c%c=%c\n",
4602                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4603                 PieceToChar(WhiteMan));
4604         else
4605             sprintf(user_move, "%c%c%c%c=%c\n",
4606                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4607                 PieceToChar(PromoPiece(moveType)));
4608         break;
4609       case WhiteDrop:
4610       case BlackDrop:
4611         sprintf(user_move, "%c@%c%c\n",
4612                 ToUpper(PieceToChar((ChessSquare) fromX)),
4613                 AAA + toX, ONE + toY);
4614         break;
4615       case NormalMove:
4616       case WhiteCapturesEnPassant:
4617       case BlackCapturesEnPassant:
4618       case IllegalMove:  /* could be a variant we don't quite understand */
4619         sprintf(user_move, "%c%c%c%c\n",
4620                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4621         break;
4622     }
4623     SendToICS(user_move);
4624     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4625         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4626 }
4627
4628 void
4629 UploadGameEvent()
4630 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4631     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4632     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4633     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4634         DisplayError("You cannot do this while you are playing or observing", 0);
4635         return;
4636     }
4637     if(gameMode != IcsExamining) { // is this ever not the case?
4638         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4639
4640         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4641             sprintf(command, "match %s", ics_handle);
4642         } else { // on FICS we must first go to general examine mode
4643             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4644         }
4645         if(gameInfo.variant != VariantNormal) {
4646             // try figure out wild number, as xboard names are not always valid on ICS
4647             for(i=1; i<=36; i++) {
4648                 sprintf(buf, "wild/%d", i);
4649                 if(StringToVariant(buf) == gameInfo.variant) break;
4650             }
4651             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4652             else if(i == 22) sprintf(buf, "%s fr\n", command);
4653             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4654         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4655         SendToICS(ics_prefix);
4656         SendToICS(buf);
4657         if(startedFromSetupPosition || backwardMostMove != 0) {
4658           fen = PositionToFEN(backwardMostMove, NULL);
4659           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4660             sprintf(buf, "loadfen %s\n", fen);
4661             SendToICS(buf);
4662           } else { // FICS: everything has to set by separate bsetup commands
4663             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4664             sprintf(buf, "bsetup fen %s\n", fen);
4665             SendToICS(buf);
4666             if(!WhiteOnMove(backwardMostMove)) {
4667                 SendToICS("bsetup tomove black\n");
4668             }
4669             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4670             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4671             SendToICS(buf);
4672             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4673             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4674             SendToICS(buf);
4675             i = boards[backwardMostMove][EP_STATUS];
4676             if(i >= 0) { // set e.p.
4677                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4678                 SendToICS(buf);
4679             }
4680             bsetup++;
4681           }
4682         }
4683       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4684             SendToICS("bsetup done\n"); // switch to normal examining.
4685     }
4686     for(i = backwardMostMove; i<last; i++) {
4687         char buf[20];
4688         sprintf(buf, "%s\n", parseList[i]);
4689         SendToICS(buf);
4690     }
4691     SendToICS(ics_prefix);
4692     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4693 }
4694
4695 void
4696 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4697      int rf, ff, rt, ft;
4698      char promoChar;
4699      char move[7];
4700 {
4701     if (rf == DROP_RANK) {
4702         sprintf(move, "%c@%c%c\n",
4703                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4704     } else {
4705         if (promoChar == 'x' || promoChar == NULLCHAR) {
4706             sprintf(move, "%c%c%c%c\n",
4707                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4708         } else {
4709             sprintf(move, "%c%c%c%c%c\n",
4710                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4711         }
4712     }
4713 }
4714
4715 void
4716 ProcessICSInitScript(f)
4717      FILE *f;
4718 {
4719     char buf[MSG_SIZ];
4720
4721     while (fgets(buf, MSG_SIZ, f)) {
4722         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4723     }
4724
4725     fclose(f);
4726 }
4727
4728
4729 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4730 void
4731 AlphaRank(char *move, int n)
4732 {
4733 //    char *p = move, c; int x, y;
4734
4735     if (appData.debugMode) {
4736         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4737     }
4738
4739     if(move[1]=='*' && 
4740        move[2]>='0' && move[2]<='9' &&
4741        move[3]>='a' && move[3]<='x'    ) {
4742         move[1] = '@';
4743         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4744         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4745     } else
4746     if(move[0]>='0' && move[0]<='9' &&
4747        move[1]>='a' && move[1]<='x' &&
4748        move[2]>='0' && move[2]<='9' &&
4749        move[3]>='a' && move[3]<='x'    ) {
4750         /* input move, Shogi -> normal */
4751         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4752         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4753         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4754         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4755     } else
4756     if(move[1]=='@' &&
4757        move[3]>='0' && move[3]<='9' &&
4758        move[2]>='a' && move[2]<='x'    ) {
4759         move[1] = '*';
4760         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4761         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4762     } else
4763     if(
4764        move[0]>='a' && move[0]<='x' &&
4765        move[3]>='0' && move[3]<='9' &&
4766        move[2]>='a' && move[2]<='x'    ) {
4767          /* output move, normal -> Shogi */
4768         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4769         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4770         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4771         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4772         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4773     }
4774     if (appData.debugMode) {
4775         fprintf(debugFP, "   out = '%s'\n", move);
4776     }
4777 }
4778
4779 char yy_textstr[8000];
4780
4781 /* Parser for moves from gnuchess, ICS, or user typein box */
4782 Boolean
4783 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4784      char *move;
4785      int moveNum;
4786      ChessMove *moveType;
4787      int *fromX, *fromY, *toX, *toY;
4788      char *promoChar;
4789 {       
4790     if (appData.debugMode) {
4791         fprintf(debugFP, "move to parse: %s\n", move);
4792     }
4793     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4794
4795     switch (*moveType) {
4796       case WhitePromotionChancellor:
4797       case BlackPromotionChancellor:
4798       case WhitePromotionArchbishop:
4799       case BlackPromotionArchbishop:
4800       case WhitePromotionQueen:
4801       case BlackPromotionQueen:
4802       case WhitePromotionRook:
4803       case BlackPromotionRook:
4804       case WhitePromotionBishop:
4805       case BlackPromotionBishop:
4806       case WhitePromotionKnight:
4807       case BlackPromotionKnight:
4808       case WhitePromotionKing:
4809       case BlackPromotionKing:
4810       case WhiteNonPromotion:
4811       case BlackNonPromotion:
4812       case NormalMove:
4813       case WhiteCapturesEnPassant:
4814       case BlackCapturesEnPassant:
4815       case WhiteKingSideCastle:
4816       case WhiteQueenSideCastle:
4817       case BlackKingSideCastle:
4818       case BlackQueenSideCastle:
4819       case WhiteKingSideCastleWild:
4820       case WhiteQueenSideCastleWild:
4821       case BlackKingSideCastleWild:
4822       case BlackQueenSideCastleWild:
4823       /* Code added by Tord: */
4824       case WhiteHSideCastleFR:
4825       case WhiteASideCastleFR:
4826       case BlackHSideCastleFR:
4827       case BlackASideCastleFR:
4828       /* End of code added by Tord */
4829       case IllegalMove:         /* bug or odd chess variant */
4830         *fromX = currentMoveString[0] - AAA;
4831         *fromY = currentMoveString[1] - ONE;
4832         *toX = currentMoveString[2] - AAA;
4833         *toY = currentMoveString[3] - ONE;
4834         *promoChar = currentMoveString[4];
4835         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4836             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4837     if (appData.debugMode) {
4838         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4839     }
4840             *fromX = *fromY = *toX = *toY = 0;
4841             return FALSE;
4842         }
4843         if (appData.testLegality) {
4844           return (*moveType != IllegalMove);
4845         } else {
4846           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4847                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4848         }
4849
4850       case WhiteDrop:
4851       case BlackDrop:
4852         *fromX = *moveType == WhiteDrop ?
4853           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4854           (int) CharToPiece(ToLower(currentMoveString[0]));
4855         *fromY = DROP_RANK;
4856         *toX = currentMoveString[2] - AAA;
4857         *toY = currentMoveString[3] - ONE;
4858         *promoChar = NULLCHAR;
4859         return TRUE;
4860
4861       case AmbiguousMove:
4862       case ImpossibleMove:
4863       case (ChessMove) 0:       /* end of file */
4864       case ElapsedTime:
4865       case Comment:
4866       case PGNTag:
4867       case NAG:
4868       case WhiteWins:
4869       case BlackWins:
4870       case GameIsDrawn:
4871       default:
4872     if (appData.debugMode) {
4873         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4874     }
4875         /* bug? */
4876         *fromX = *fromY = *toX = *toY = 0;
4877         *promoChar = NULLCHAR;
4878         return FALSE;
4879     }
4880 }
4881
4882
4883 void
4884 ParsePV(char *pv, Boolean storeComments)
4885 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4886   int fromX, fromY, toX, toY; char promoChar;
4887   ChessMove moveType;
4888   Boolean valid;
4889   int nr = 0;
4890
4891   endPV = forwardMostMove;
4892   do {
4893     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4894     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4895     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4896 if(appData.debugMode){
4897 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);
4898 }
4899     if(!valid && nr == 0 &&
4900        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4901         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4902         // Hande case where played move is different from leading PV move
4903         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4904         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4905         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4906         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4907           endPV += 2; // if position different, keep this
4908           moveList[endPV-1][0] = fromX + AAA;
4909           moveList[endPV-1][1] = fromY + ONE;
4910           moveList[endPV-1][2] = toX + AAA;
4911           moveList[endPV-1][3] = toY + ONE;
4912           parseList[endPV-1][0] = NULLCHAR;
4913           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4914         }
4915       }
4916     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4917     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4918     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4919     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4920         valid++; // allow comments in PV
4921         continue;
4922     }
4923     nr++;
4924     if(endPV+1 > framePtr) break; // no space, truncate
4925     if(!valid) break;
4926     endPV++;
4927     CopyBoard(boards[endPV], boards[endPV-1]);
4928     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4929     moveList[endPV-1][0] = fromX + AAA;
4930     moveList[endPV-1][1] = fromY + ONE;
4931     moveList[endPV-1][2] = toX + AAA;
4932     moveList[endPV-1][3] = toY + ONE;
4933     if(storeComments)
4934         CoordsToAlgebraic(boards[endPV - 1],
4935                              PosFlags(endPV - 1),
4936                              fromY, fromX, toY, toX, promoChar,
4937                              parseList[endPV - 1]);
4938     else
4939         parseList[endPV-1][0] = NULLCHAR;
4940   } while(valid);
4941   currentMove = endPV;
4942   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4943   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4944                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4945   DrawPosition(TRUE, boards[currentMove]);
4946 }
4947
4948 static int lastX, lastY;
4949
4950 Boolean
4951 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4952 {
4953         int startPV;
4954         char *p;
4955
4956         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4957         lastX = x; lastY = y;
4958         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4959         startPV = index;
4960         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4961         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4962         index = startPV;
4963         do{ while(buf[index] && buf[index] != '\n') index++;
4964         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4965         buf[index] = 0;
4966         ParsePV(buf+startPV, FALSE);
4967         *start = startPV; *end = index-1;
4968         return TRUE;
4969 }
4970
4971 Boolean
4972 LoadPV(int x, int y)
4973 { // called on right mouse click to load PV
4974   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4975   lastX = x; lastY = y;
4976   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4977   return TRUE;
4978 }
4979
4980 void
4981 UnLoadPV()
4982 {
4983   if(endPV < 0) return;
4984   endPV = -1;
4985   currentMove = forwardMostMove;
4986   ClearPremoveHighlights();
4987   DrawPosition(TRUE, boards[currentMove]);
4988 }
4989
4990 void
4991 MovePV(int x, int y, int h)
4992 { // step through PV based on mouse coordinates (called on mouse move)
4993   int margin = h>>3, step = 0;
4994
4995   if(endPV < 0) return;
4996   // we must somehow check if right button is still down (might be released off board!)
4997   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4998   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4999   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5000   if(!step) return;
5001   lastX = x; lastY = y;
5002   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5003   currentMove += step;
5004   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5005   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5006                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5007   DrawPosition(FALSE, boards[currentMove]);
5008 }
5009
5010
5011 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5012 // All positions will have equal probability, but the current method will not provide a unique
5013 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5014 #define DARK 1
5015 #define LITE 2
5016 #define ANY 3
5017
5018 int squaresLeft[4];
5019 int piecesLeft[(int)BlackPawn];
5020 int seed, nrOfShuffles;
5021
5022 void GetPositionNumber()
5023 {       // sets global variable seed
5024         int i;
5025
5026         seed = appData.defaultFrcPosition;
5027         if(seed < 0) { // randomize based on time for negative FRC position numbers
5028                 for(i=0; i<50; i++) seed += random();
5029                 seed = random() ^ random() >> 8 ^ random() << 8;
5030                 if(seed<0) seed = -seed;
5031         }
5032 }
5033
5034 int put(Board board, int pieceType, int rank, int n, int shade)
5035 // put the piece on the (n-1)-th empty squares of the given shade
5036 {
5037         int i;
5038
5039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5040                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5041                         board[rank][i] = (ChessSquare) pieceType;
5042                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5043                         squaresLeft[ANY]--;
5044                         piecesLeft[pieceType]--; 
5045                         return i;
5046                 }
5047         }
5048         return -1;
5049 }
5050
5051
5052 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5053 // calculate where the next piece goes, (any empty square), and put it there
5054 {
5055         int i;
5056
5057         i = seed % squaresLeft[shade];
5058         nrOfShuffles *= squaresLeft[shade];
5059         seed /= squaresLeft[shade];
5060         put(board, pieceType, rank, i, shade);
5061 }
5062
5063 void AddTwoPieces(Board board, int pieceType, int rank)
5064 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5065 {
5066         int i, n=squaresLeft[ANY], j=n-1, k;
5067
5068         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5069         i = seed % k;  // pick one
5070         nrOfShuffles *= k;
5071         seed /= k;
5072         while(i >= j) i -= j--;
5073         j = n - 1 - j; i += j;
5074         put(board, pieceType, rank, j, ANY);
5075         put(board, pieceType, rank, i, ANY);
5076 }
5077
5078 void SetUpShuffle(Board board, int number)
5079 {
5080         int i, p, first=1;
5081
5082         GetPositionNumber(); nrOfShuffles = 1;
5083
5084         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5085         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5086         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5087
5088         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5089
5090         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5091             p = (int) board[0][i];
5092             if(p < (int) BlackPawn) piecesLeft[p] ++;
5093             board[0][i] = EmptySquare;
5094         }
5095
5096         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5097             // shuffles restricted to allow normal castling put KRR first
5098             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5099                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5100             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5101                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5102             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5103                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5104             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5105                 put(board, WhiteRook, 0, 0, ANY);
5106             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5107         }
5108
5109         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5110             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5111             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5112                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5113                 while(piecesLeft[p] >= 2) {
5114                     AddOnePiece(board, p, 0, LITE);
5115                     AddOnePiece(board, p, 0, DARK);
5116                 }
5117                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5118             }
5119
5120         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5121             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5122             // but we leave King and Rooks for last, to possibly obey FRC restriction
5123             if(p == (int)WhiteRook) continue;
5124             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5125             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5126         }
5127
5128         // now everything is placed, except perhaps King (Unicorn) and Rooks
5129
5130         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5131             // Last King gets castling rights
5132             while(piecesLeft[(int)WhiteUnicorn]) {
5133                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5134                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5135             }
5136
5137             while(piecesLeft[(int)WhiteKing]) {
5138                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5139                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5140             }
5141
5142
5143         } else {
5144             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5145             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5146         }
5147
5148         // Only Rooks can be left; simply place them all
5149         while(piecesLeft[(int)WhiteRook]) {
5150                 i = put(board, WhiteRook, 0, 0, ANY);
5151                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5152                         if(first) {
5153                                 first=0;
5154                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5155                         }
5156                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5157                 }
5158         }
5159         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5160             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5161         }
5162
5163         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5164 }
5165
5166 int SetCharTable( char *table, const char * map )
5167 /* [HGM] moved here from winboard.c because of its general usefulness */
5168 /*       Basically a safe strcpy that uses the last character as King */
5169 {
5170     int result = FALSE; int NrPieces;
5171
5172     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5173                     && NrPieces >= 12 && !(NrPieces&1)) {
5174         int i; /* [HGM] Accept even length from 12 to 34 */
5175
5176         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5177         for( i=0; i<NrPieces/2-1; i++ ) {
5178             table[i] = map[i];
5179             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5180         }
5181         table[(int) WhiteKing]  = map[NrPieces/2-1];
5182         table[(int) BlackKing]  = map[NrPieces-1];
5183
5184         result = TRUE;
5185     }
5186
5187     return result;
5188 }
5189
5190 void Prelude(Board board)
5191 {       // [HGM] superchess: random selection of exo-pieces
5192         int i, j, k; ChessSquare p; 
5193         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5194
5195         GetPositionNumber(); // use FRC position number
5196
5197         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5198             SetCharTable(pieceToChar, appData.pieceToCharTable);
5199             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5200                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5201         }
5202
5203         j = seed%4;                 seed /= 4; 
5204         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5205         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5206         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5207         j = seed%3 + (seed%3 >= j); seed /= 3; 
5208         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5209         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5210         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5211         j = seed%3;                 seed /= 3; 
5212         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5213         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5214         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5215         j = seed%2 + (seed%2 >= j); seed /= 2; 
5216         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5217         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5218         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5219         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5220         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5221         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5222         put(board, exoPieces[0],    0, 0, ANY);
5223         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5224 }
5225
5226 void
5227 InitPosition(redraw)
5228      int redraw;
5229 {
5230     ChessSquare (* pieces)[BOARD_FILES];
5231     int i, j, pawnRow, overrule,
5232     oldx = gameInfo.boardWidth,
5233     oldy = gameInfo.boardHeight,
5234     oldh = gameInfo.holdingsWidth,
5235     oldv = gameInfo.variant;
5236
5237     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5238
5239     /* [AS] Initialize pv info list [HGM] and game status */
5240     {
5241         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5242             pvInfoList[i].depth = 0;
5243             boards[i][EP_STATUS] = EP_NONE;
5244             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5245         }
5246
5247         initialRulePlies = 0; /* 50-move counter start */
5248
5249         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5250         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5251     }
5252
5253     
5254     /* [HGM] logic here is completely changed. In stead of full positions */
5255     /* the initialized data only consist of the two backranks. The switch */
5256     /* selects which one we will use, which is than copied to the Board   */
5257     /* initialPosition, which for the rest is initialized by Pawns and    */
5258     /* empty squares. This initial position is then copied to boards[0],  */
5259     /* possibly after shuffling, so that it remains available.            */
5260
5261     gameInfo.holdingsWidth = 0; /* default board sizes */
5262     gameInfo.boardWidth    = 8;
5263     gameInfo.boardHeight   = 8;
5264     gameInfo.holdingsSize  = 0;
5265     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5266     for(i=0; i<BOARD_FILES-2; i++)
5267       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5268     initialPosition[EP_STATUS] = EP_NONE;
5269     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5270     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5271          SetCharTable(pieceNickName, appData.pieceNickNames);
5272     else SetCharTable(pieceNickName, "............");
5273
5274     switch (gameInfo.variant) {
5275     case VariantFischeRandom:
5276       shuffleOpenings = TRUE;
5277     default:
5278       pieces = FIDEArray;
5279       break;
5280     case VariantShatranj:
5281       pieces = ShatranjArray;
5282       nrCastlingRights = 0;
5283       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5284       break;
5285     case VariantMakruk:
5286       pieces = makrukArray;
5287       nrCastlingRights = 0;
5288       startedFromSetupPosition = TRUE;
5289       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5290       break;
5291     case VariantTwoKings:
5292       pieces = twoKingsArray;
5293       break;
5294     case VariantCapaRandom:
5295       shuffleOpenings = TRUE;
5296     case VariantCapablanca:
5297       pieces = CapablancaArray;
5298       gameInfo.boardWidth = 10;
5299       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5300       break;
5301     case VariantGothic:
5302       pieces = GothicArray;
5303       gameInfo.boardWidth = 10;
5304       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5305       break;
5306     case VariantJanus:
5307       pieces = JanusArray;
5308       gameInfo.boardWidth = 10;
5309       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5310       nrCastlingRights = 6;
5311         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5312         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5313         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5314         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5315         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5316         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5317       break;
5318     case VariantFalcon:
5319       pieces = FalconArray;
5320       gameInfo.boardWidth = 10;
5321       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5322       break;
5323     case VariantXiangqi:
5324       pieces = XiangqiArray;
5325       gameInfo.boardWidth  = 9;
5326       gameInfo.boardHeight = 10;
5327       nrCastlingRights = 0;
5328       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5329       break;
5330     case VariantShogi:
5331       pieces = ShogiArray;
5332       gameInfo.boardWidth  = 9;
5333       gameInfo.boardHeight = 9;
5334       gameInfo.holdingsSize = 7;
5335       nrCastlingRights = 0;
5336       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5337       break;
5338     case VariantCourier:
5339       pieces = CourierArray;
5340       gameInfo.boardWidth  = 12;
5341       nrCastlingRights = 0;
5342       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5343       break;
5344     case VariantKnightmate:
5345       pieces = KnightmateArray;
5346       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5347       break;
5348     case VariantFairy:
5349       pieces = fairyArray;
5350       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5351       break;
5352     case VariantGreat:
5353       pieces = GreatArray;
5354       gameInfo.boardWidth = 10;
5355       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5356       gameInfo.holdingsSize = 8;
5357       break;
5358     case VariantSuper:
5359       pieces = FIDEArray;
5360       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5361       gameInfo.holdingsSize = 8;
5362       startedFromSetupPosition = TRUE;
5363       break;
5364     case VariantCrazyhouse:
5365     case VariantBughouse:
5366       pieces = FIDEArray;
5367       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5368       gameInfo.holdingsSize = 5;
5369       break;
5370     case VariantWildCastle:
5371       pieces = FIDEArray;
5372       /* !!?shuffle with kings guaranteed to be on d or e file */
5373       shuffleOpenings = 1;
5374       break;
5375     case VariantNoCastle:
5376       pieces = FIDEArray;
5377       nrCastlingRights = 0;
5378       /* !!?unconstrained back-rank shuffle */
5379       shuffleOpenings = 1;
5380       break;
5381     }
5382
5383     overrule = 0;
5384     if(appData.NrFiles >= 0) {
5385         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5386         gameInfo.boardWidth = appData.NrFiles;
5387     }
5388     if(appData.NrRanks >= 0) {
5389         gameInfo.boardHeight = appData.NrRanks;
5390     }
5391     if(appData.holdingsSize >= 0) {
5392         i = appData.holdingsSize;
5393         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5394         gameInfo.holdingsSize = i;
5395     }
5396     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5397     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5398         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5399
5400     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5401     if(pawnRow < 1) pawnRow = 1;
5402     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5403
5404     /* User pieceToChar list overrules defaults */
5405     if(appData.pieceToCharTable != NULL)
5406         SetCharTable(pieceToChar, appData.pieceToCharTable);
5407
5408     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5409
5410         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5411             s = (ChessSquare) 0; /* account holding counts in guard band */
5412         for( i=0; i<BOARD_HEIGHT; i++ )
5413             initialPosition[i][j] = s;
5414
5415         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5416         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5417         initialPosition[pawnRow][j] = WhitePawn;
5418         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5419         if(gameInfo.variant == VariantXiangqi) {
5420             if(j&1) {
5421                 initialPosition[pawnRow][j] = 
5422                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5423                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5424                    initialPosition[2][j] = WhiteCannon;
5425                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5426                 }
5427             }
5428         }
5429         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5430     }
5431     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5432
5433             j=BOARD_LEFT+1;
5434             initialPosition[1][j] = WhiteBishop;
5435             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5436             j=BOARD_RGHT-2;
5437             initialPosition[1][j] = WhiteRook;
5438             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5439     }
5440
5441     if( nrCastlingRights == -1) {
5442         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5443         /*       This sets default castling rights from none to normal corners   */
5444         /* Variants with other castling rights must set them themselves above    */
5445         nrCastlingRights = 6;
5446        
5447         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5448         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5449         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5450         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5451         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5452         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5453      }
5454
5455      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5456      if(gameInfo.variant == VariantGreat) { // promotion commoners
5457         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5458         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5459         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5460         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5461      }
5462   if (appData.debugMode) {
5463     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5464   }
5465     if(shuffleOpenings) {
5466         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5467         startedFromSetupPosition = TRUE;
5468     }
5469     if(startedFromPositionFile) {
5470       /* [HGM] loadPos: use PositionFile for every new game */
5471       CopyBoard(initialPosition, filePosition);
5472       for(i=0; i<nrCastlingRights; i++)
5473           initialRights[i] = filePosition[CASTLING][i];
5474       startedFromSetupPosition = TRUE;
5475     }
5476
5477     CopyBoard(boards[0], initialPosition);
5478
5479     if(oldx != gameInfo.boardWidth ||
5480        oldy != gameInfo.boardHeight ||
5481        oldh != gameInfo.holdingsWidth
5482 #ifdef GOTHIC
5483        || oldv == VariantGothic ||        // For licensing popups
5484        gameInfo.variant == VariantGothic
5485 #endif
5486 #ifdef FALCON
5487        || oldv == VariantFalcon ||
5488        gameInfo.variant == VariantFalcon
5489 #endif
5490                                          )
5491             InitDrawingSizes(-2 ,0);
5492
5493     if (redraw)
5494       DrawPosition(TRUE, boards[currentMove]);
5495 }
5496
5497 void
5498 SendBoard(cps, moveNum)
5499      ChessProgramState *cps;
5500      int moveNum;
5501 {
5502     char message[MSG_SIZ];
5503     
5504     if (cps->useSetboard) {
5505       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5506       sprintf(message, "setboard %s\n", fen);
5507       SendToProgram(message, cps);
5508       free(fen);
5509
5510     } else {
5511       ChessSquare *bp;
5512       int i, j;
5513       /* Kludge to set black to move, avoiding the troublesome and now
5514        * deprecated "black" command.
5515        */
5516       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5517
5518       SendToProgram("edit\n", cps);
5519       SendToProgram("#\n", cps);
5520       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5521         bp = &boards[moveNum][i][BOARD_LEFT];
5522         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5523           if ((int) *bp < (int) BlackPawn) {
5524             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5525                     AAA + j, ONE + i);
5526             if(message[0] == '+' || message[0] == '~') {
5527                 sprintf(message, "%c%c%c+\n",
5528                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5529                         AAA + j, ONE + i);
5530             }
5531             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5532                 message[1] = BOARD_RGHT   - 1 - j + '1';
5533                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5534             }
5535             SendToProgram(message, cps);
5536           }
5537         }
5538       }
5539     
5540       SendToProgram("c\n", cps);
5541       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5542         bp = &boards[moveNum][i][BOARD_LEFT];
5543         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5544           if (((int) *bp != (int) EmptySquare)
5545               && ((int) *bp >= (int) BlackPawn)) {
5546             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5547                     AAA + j, ONE + i);
5548             if(message[0] == '+' || message[0] == '~') {
5549                 sprintf(message, "%c%c%c+\n",
5550                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5551                         AAA + j, ONE + i);
5552             }
5553             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5554                 message[1] = BOARD_RGHT   - 1 - j + '1';
5555                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5556             }
5557             SendToProgram(message, cps);
5558           }
5559         }
5560       }
5561     
5562       SendToProgram(".\n", cps);
5563     }
5564     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5565 }
5566
5567 static int autoQueen; // [HGM] oneclick
5568
5569 int
5570 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5571 {
5572     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5573     /* [HGM] add Shogi promotions */
5574     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5575     ChessSquare piece;
5576     ChessMove moveType;
5577     Boolean premove;
5578
5579     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5580     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5581
5582     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5583       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5584         return FALSE;
5585
5586     piece = boards[currentMove][fromY][fromX];
5587     if(gameInfo.variant == VariantShogi) {
5588         promotionZoneSize = 3;
5589         highestPromotingPiece = (int)WhiteFerz;
5590     } else if(gameInfo.variant == VariantMakruk) {
5591         promotionZoneSize = 3;
5592     }
5593
5594     // next weed out all moves that do not touch the promotion zone at all
5595     if((int)piece >= BlackPawn) {
5596         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5597              return FALSE;
5598         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5599     } else {
5600         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5601            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5602     }
5603
5604     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5605
5606     // weed out mandatory Shogi promotions
5607     if(gameInfo.variant == VariantShogi) {
5608         if(piece >= BlackPawn) {
5609             if(toY == 0 && piece == BlackPawn ||
5610                toY == 0 && piece == BlackQueen ||
5611                toY <= 1 && piece == BlackKnight) {
5612                 *promoChoice = '+';
5613                 return FALSE;
5614             }
5615         } else {
5616             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5617                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5618                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5619                 *promoChoice = '+';
5620                 return FALSE;
5621             }
5622         }
5623     }
5624
5625     // weed out obviously illegal Pawn moves
5626     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5627         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5628         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5629         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5630         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5631         // note we are not allowed to test for valid (non-)capture, due to premove
5632     }
5633
5634     // we either have a choice what to promote to, or (in Shogi) whether to promote
5635     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5636         *promoChoice = PieceToChar(BlackFerz);  // no choice
5637         return FALSE;
5638     }
5639     if(autoQueen) { // predetermined
5640         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5641              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5642         else *promoChoice = PieceToChar(BlackQueen);
5643         return FALSE;
5644     }
5645
5646     // suppress promotion popup on illegal moves that are not premoves
5647     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5648               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5649     if(appData.testLegality && !premove) {
5650         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5651                         fromY, fromX, toY, toX, NULLCHAR);
5652         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5653            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5654             return FALSE;
5655     }
5656
5657     return TRUE;
5658 }
5659
5660 int
5661 InPalace(row, column)
5662      int row, column;
5663 {   /* [HGM] for Xiangqi */
5664     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5665          column < (BOARD_WIDTH + 4)/2 &&
5666          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5667     return FALSE;
5668 }
5669
5670 int
5671 PieceForSquare (x, y)
5672      int x;
5673      int y;
5674 {
5675   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5676      return -1;
5677   else
5678      return boards[currentMove][y][x];
5679 }
5680
5681 int
5682 OKToStartUserMove(x, y)
5683      int x, y;
5684 {
5685     ChessSquare from_piece;
5686     int white_piece;
5687
5688     if (matchMode) return FALSE;
5689     if (gameMode == EditPosition) return TRUE;
5690
5691     if (x >= 0 && y >= 0)
5692       from_piece = boards[currentMove][y][x];
5693     else
5694       from_piece = EmptySquare;
5695
5696     if (from_piece == EmptySquare) return FALSE;
5697
5698     white_piece = (int)from_piece >= (int)WhitePawn &&
5699       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5700
5701     switch (gameMode) {
5702       case PlayFromGameFile:
5703       case AnalyzeFile:
5704       case TwoMachinesPlay:
5705       case EndOfGame:
5706         return FALSE;
5707
5708       case IcsObserving:
5709       case IcsIdle:
5710         return FALSE;
5711
5712       case MachinePlaysWhite:
5713       case IcsPlayingBlack:
5714         if (appData.zippyPlay) return FALSE;
5715         if (white_piece) {
5716             DisplayMoveError(_("You are playing Black"));
5717             return FALSE;
5718         }
5719         break;
5720
5721       case MachinePlaysBlack:
5722       case IcsPlayingWhite:
5723         if (appData.zippyPlay) return FALSE;
5724         if (!white_piece) {
5725             DisplayMoveError(_("You are playing White"));
5726             return FALSE;
5727         }
5728         break;
5729
5730       case EditGame:
5731         if (!white_piece && WhiteOnMove(currentMove)) {
5732             DisplayMoveError(_("It is White's turn"));
5733             return FALSE;
5734         }           
5735         if (white_piece && !WhiteOnMove(currentMove)) {
5736             DisplayMoveError(_("It is Black's turn"));
5737             return FALSE;
5738         }           
5739         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5740             /* Editing correspondence game history */
5741             /* Could disallow this or prompt for confirmation */
5742             cmailOldMove = -1;
5743         }
5744         break;
5745
5746       case BeginningOfGame:
5747         if (appData.icsActive) return FALSE;
5748         if (!appData.noChessProgram) {
5749             if (!white_piece) {
5750                 DisplayMoveError(_("You are playing White"));
5751                 return FALSE;
5752             }
5753         }
5754         break;
5755         
5756       case Training:
5757         if (!white_piece && WhiteOnMove(currentMove)) {
5758             DisplayMoveError(_("It is White's turn"));
5759             return FALSE;
5760         }           
5761         if (white_piece && !WhiteOnMove(currentMove)) {
5762             DisplayMoveError(_("It is Black's turn"));
5763             return FALSE;
5764         }           
5765         break;
5766
5767       default:
5768       case IcsExamining:
5769         break;
5770     }
5771     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5772         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5773         && gameMode != AnalyzeFile && gameMode != Training) {
5774         DisplayMoveError(_("Displayed position is not current"));
5775         return FALSE;
5776     }
5777     return TRUE;
5778 }
5779
5780 Boolean
5781 OnlyMove(int *x, int *y, Boolean captures) {
5782     DisambiguateClosure cl;
5783     if (appData.zippyPlay) return FALSE;
5784     switch(gameMode) {
5785       case MachinePlaysBlack:
5786       case IcsPlayingWhite:
5787       case BeginningOfGame:
5788         if(!WhiteOnMove(currentMove)) return FALSE;
5789         break;
5790       case MachinePlaysWhite:
5791       case IcsPlayingBlack:
5792         if(WhiteOnMove(currentMove)) return FALSE;
5793         break;
5794       default:
5795         return FALSE;
5796     }
5797     cl.pieceIn = EmptySquare; 
5798     cl.rfIn = *y;
5799     cl.ffIn = *x;
5800     cl.rtIn = -1;
5801     cl.ftIn = -1;
5802     cl.promoCharIn = NULLCHAR;
5803     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5804     if( cl.kind == NormalMove ||
5805         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5806         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5807         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5808         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5809       fromX = cl.ff;
5810       fromY = cl.rf;
5811       *x = cl.ft;
5812       *y = cl.rt;
5813       return TRUE;
5814     }
5815     if(cl.kind != ImpossibleMove) return FALSE;
5816     cl.pieceIn = EmptySquare;
5817     cl.rfIn = -1;
5818     cl.ffIn = -1;
5819     cl.rtIn = *y;
5820     cl.ftIn = *x;
5821     cl.promoCharIn = NULLCHAR;
5822     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5823     if( cl.kind == NormalMove ||
5824         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5825         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5826         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5827         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5828       fromX = cl.ff;
5829       fromY = cl.rf;
5830       *x = cl.ft;
5831       *y = cl.rt;
5832       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5833       return TRUE;
5834     }
5835     return FALSE;
5836 }
5837
5838 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5839 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5840 int lastLoadGameUseList = FALSE;
5841 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5842 ChessMove lastLoadGameStart = (ChessMove) 0;
5843
5844 void
5845 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5846      int fromX, fromY, toX, toY;
5847      int promoChar;
5848 {
5849     ChessMove moveType;
5850     ChessSquare pdown, pup;
5851
5852     /* Check if the user is playing in turn.  This is complicated because we
5853        let the user "pick up" a piece before it is his turn.  So the piece he
5854        tried to pick up may have been captured by the time he puts it down!
5855        Therefore we use the color the user is supposed to be playing in this
5856        test, not the color of the piece that is currently on the starting
5857        square---except in EditGame mode, where the user is playing both
5858        sides; fortunately there the capture race can't happen.  (It can
5859        now happen in IcsExamining mode, but that's just too bad.  The user
5860        will get a somewhat confusing message in that case.)
5861        */
5862
5863     switch (gameMode) {
5864       case PlayFromGameFile:
5865       case AnalyzeFile:
5866       case TwoMachinesPlay:
5867       case EndOfGame:
5868       case IcsObserving:
5869       case IcsIdle:
5870         /* We switched into a game mode where moves are not accepted,
5871            perhaps while the mouse button was down. */
5872         return;
5873
5874       case MachinePlaysWhite:
5875         /* User is moving for Black */
5876         if (WhiteOnMove(currentMove)) {
5877             DisplayMoveError(_("It is White's turn"));
5878             return;
5879         }
5880         break;
5881
5882       case MachinePlaysBlack:
5883         /* User is moving for White */
5884         if (!WhiteOnMove(currentMove)) {
5885             DisplayMoveError(_("It is Black's turn"));
5886             return;
5887         }
5888         break;
5889
5890       case EditGame:
5891       case IcsExamining:
5892       case BeginningOfGame:
5893       case AnalyzeMode:
5894       case Training:
5895         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5896             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5897             /* User is moving for Black */
5898             if (WhiteOnMove(currentMove)) {
5899                 DisplayMoveError(_("It is White's turn"));
5900                 return;
5901             }
5902         } else {
5903             /* User is moving for White */
5904             if (!WhiteOnMove(currentMove)) {
5905                 DisplayMoveError(_("It is Black's turn"));
5906                 return;
5907             }
5908         }
5909         break;
5910
5911       case IcsPlayingBlack:
5912         /* User is moving for Black */
5913         if (WhiteOnMove(currentMove)) {
5914             if (!appData.premove) {
5915                 DisplayMoveError(_("It is White's turn"));
5916             } else if (toX >= 0 && toY >= 0) {
5917                 premoveToX = toX;
5918                 premoveToY = toY;
5919                 premoveFromX = fromX;
5920                 premoveFromY = fromY;
5921                 premovePromoChar = promoChar;
5922                 gotPremove = 1;
5923                 if (appData.debugMode) 
5924                     fprintf(debugFP, "Got premove: fromX %d,"
5925                             "fromY %d, toX %d, toY %d\n",
5926                             fromX, fromY, toX, toY);
5927             }
5928             return;
5929         }
5930         break;
5931
5932       case IcsPlayingWhite:
5933         /* User is moving for White */
5934         if (!WhiteOnMove(currentMove)) {
5935             if (!appData.premove) {
5936                 DisplayMoveError(_("It is Black's turn"));
5937             } else if (toX >= 0 && toY >= 0) {
5938                 premoveToX = toX;
5939                 premoveToY = toY;
5940                 premoveFromX = fromX;
5941                 premoveFromY = fromY;
5942                 premovePromoChar = promoChar;
5943                 gotPremove = 1;
5944                 if (appData.debugMode) 
5945                     fprintf(debugFP, "Got premove: fromX %d,"
5946                             "fromY %d, toX %d, toY %d\n",
5947                             fromX, fromY, toX, toY);
5948             }
5949             return;
5950         }
5951         break;
5952
5953       default:
5954         break;
5955
5956       case EditPosition:
5957         /* EditPosition, empty square, or different color piece;
5958            click-click move is possible */
5959         if (toX == -2 || toY == -2) {
5960             boards[0][fromY][fromX] = EmptySquare;
5961             DrawPosition(FALSE, boards[currentMove]);
5962             return;
5963         } else if (toX >= 0 && toY >= 0) {
5964             boards[0][toY][toX] = boards[0][fromY][fromX];
5965             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5966                 if(boards[0][fromY][0] != EmptySquare) {
5967                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5968                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5969                 }
5970             } else
5971             if(fromX == BOARD_RGHT+1) {
5972                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5973                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5974                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5975                 }
5976             } else
5977             boards[0][fromY][fromX] = EmptySquare;
5978             DrawPosition(FALSE, boards[currentMove]);
5979             return;
5980         }
5981         return;
5982     }
5983
5984     if(toX < 0 || toY < 0) return;
5985     pdown = boards[currentMove][fromY][fromX];
5986     pup = boards[currentMove][toY][toX];
5987
5988     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
5989     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5990          if( pup != EmptySquare ) return;
5991          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5992            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5993                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5994            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5995            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5996            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5997            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5998          fromY = DROP_RANK;
5999     }
6000
6001     /* [HGM] always test for legality, to get promotion info */
6002     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6003                                          fromY, fromX, toY, toX, promoChar);
6004     /* [HGM] but possibly ignore an IllegalMove result */
6005     if (appData.testLegality) {
6006         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6007             DisplayMoveError(_("Illegal move"));
6008             return;
6009         }
6010     }
6011
6012     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6013 }
6014
6015 /* Common tail of UserMoveEvent and DropMenuEvent */
6016 int
6017 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6018      ChessMove moveType;
6019      int fromX, fromY, toX, toY;
6020      /*char*/int promoChar;
6021 {
6022     char *bookHit = 0;
6023
6024     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6025         // [HGM] superchess: suppress promotions to non-available piece
6026         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6027         if(WhiteOnMove(currentMove)) {
6028             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6029         } else {
6030             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6031         }
6032     }
6033
6034     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6035        move type in caller when we know the move is a legal promotion */
6036     if(moveType == NormalMove && promoChar)
6037         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6038
6039     /* [HGM] <popupFix> The following if has been moved here from
6040        UserMoveEvent(). Because it seemed to belong here (why not allow
6041        piece drops in training games?), and because it can only be
6042        performed after it is known to what we promote. */
6043     if (gameMode == Training) {
6044       /* compare the move played on the board to the next move in the
6045        * game. If they match, display the move and the opponent's response. 
6046        * If they don't match, display an error message.
6047        */
6048       int saveAnimate;
6049       Board testBoard;
6050       CopyBoard(testBoard, boards[currentMove]);
6051       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6052
6053       if (CompareBoards(testBoard, boards[currentMove+1])) {
6054         ForwardInner(currentMove+1);
6055
6056         /* Autoplay the opponent's response.
6057          * if appData.animate was TRUE when Training mode was entered,
6058          * the response will be animated.
6059          */
6060         saveAnimate = appData.animate;
6061         appData.animate = animateTraining;
6062         ForwardInner(currentMove+1);
6063         appData.animate = saveAnimate;
6064
6065         /* check for the end of the game */
6066         if (currentMove >= forwardMostMove) {
6067           gameMode = PlayFromGameFile;
6068           ModeHighlight();
6069           SetTrainingModeOff();
6070           DisplayInformation(_("End of game"));
6071         }
6072       } else {
6073         DisplayError(_("Incorrect move"), 0);
6074       }
6075       return 1;
6076     }
6077
6078   /* Ok, now we know that the move is good, so we can kill
6079      the previous line in Analysis Mode */
6080   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6081                                 && currentMove < forwardMostMove) {
6082     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6083   }
6084
6085   /* If we need the chess program but it's dead, restart it */
6086   ResurrectChessProgram();
6087
6088   /* A user move restarts a paused game*/
6089   if (pausing)
6090     PauseEvent();
6091
6092   thinkOutput[0] = NULLCHAR;
6093
6094   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6095
6096   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6097
6098   if (gameMode == BeginningOfGame) {
6099     if (appData.noChessProgram) {
6100       gameMode = EditGame;
6101       SetGameInfo();
6102     } else {
6103       char buf[MSG_SIZ];
6104       gameMode = MachinePlaysBlack;
6105       StartClocks();
6106       SetGameInfo();
6107       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6108       DisplayTitle(buf);
6109       if (first.sendName) {
6110         sprintf(buf, "name %s\n", gameInfo.white);
6111         SendToProgram(buf, &first);
6112       }
6113       StartClocks();
6114     }
6115     ModeHighlight();
6116   }
6117
6118   /* Relay move to ICS or chess engine */
6119   if (appData.icsActive) {
6120     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6121         gameMode == IcsExamining) {
6122       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6123         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6124         SendToICS("draw ");
6125         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6126       }
6127       // also send plain move, in case ICS does not understand atomic claims
6128       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6129       ics_user_moved = 1;
6130     }
6131   } else {
6132     if (first.sendTime && (gameMode == BeginningOfGame ||
6133                            gameMode == MachinePlaysWhite ||
6134                            gameMode == MachinePlaysBlack)) {
6135       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6136     }
6137     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6138          // [HGM] book: if program might be playing, let it use book
6139         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6140         first.maybeThinking = TRUE;
6141     } else SendMoveToProgram(forwardMostMove-1, &first);
6142     if (currentMove == cmailOldMove + 1) {
6143       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6144     }
6145   }
6146
6147   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6148
6149   switch (gameMode) {
6150   case EditGame:
6151     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6152     case MT_NONE:
6153     case MT_CHECK:
6154       break;
6155     case MT_CHECKMATE:
6156     case MT_STAINMATE:
6157       if (WhiteOnMove(currentMove)) {
6158         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6159       } else {
6160         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6161       }
6162       break;
6163     case MT_STALEMATE:
6164       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6165       break;
6166     }
6167     break;
6168     
6169   case MachinePlaysBlack:
6170   case MachinePlaysWhite:
6171     /* disable certain menu options while machine is thinking */
6172     SetMachineThinkingEnables();
6173     break;
6174
6175   default:
6176     break;
6177   }
6178
6179   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6180         
6181   if(bookHit) { // [HGM] book: simulate book reply
6182         static char bookMove[MSG_SIZ]; // a bit generous?
6183
6184         programStats.nodes = programStats.depth = programStats.time = 
6185         programStats.score = programStats.got_only_move = 0;
6186         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6187
6188         strcpy(bookMove, "move ");
6189         strcat(bookMove, bookHit);
6190         HandleMachineMove(bookMove, &first);
6191   }
6192   return 1;
6193 }
6194
6195 void
6196 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6197      Board board;
6198      int flags;
6199      ChessMove kind;
6200      int rf, ff, rt, ft;
6201      VOIDSTAR closure;
6202 {
6203     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6204     Markers *m = (Markers *) closure;
6205     if(rf == fromY && ff == fromX)
6206         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6207                          || kind == WhiteCapturesEnPassant
6208                          || kind == BlackCapturesEnPassant);
6209     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6210 }
6211
6212 void
6213 MarkTargetSquares(int clear)
6214 {
6215   int x, y;
6216   if(!appData.markers || !appData.highlightDragging || 
6217      !appData.testLegality || gameMode == EditPosition) return;
6218   if(clear) {
6219     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6220   } else {
6221     int capt = 0;
6222     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6223     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6224       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6225       if(capt)
6226       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6227     }
6228   }
6229   DrawPosition(TRUE, NULL);
6230 }
6231
6232 void LeftClick(ClickType clickType, int xPix, int yPix)
6233 {
6234     int x, y;
6235     Boolean saveAnimate;
6236     static int second = 0, promotionChoice = 0, dragging = 0;
6237     char promoChoice = NULLCHAR;
6238
6239     if(appData.seekGraph && appData.icsActive && loggedOn &&
6240         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6241         SeekGraphClick(clickType, xPix, yPix, 0);
6242         return;
6243     }
6244
6245     if (clickType == Press) ErrorPopDown();
6246     MarkTargetSquares(1);
6247
6248     x = EventToSquare(xPix, BOARD_WIDTH);
6249     y = EventToSquare(yPix, BOARD_HEIGHT);
6250     if (!flipView && y >= 0) {
6251         y = BOARD_HEIGHT - 1 - y;
6252     }
6253     if (flipView && x >= 0) {
6254         x = BOARD_WIDTH - 1 - x;
6255     }
6256
6257     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6258         if(clickType == Release) return; // ignore upclick of click-click destination
6259         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6260         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6261         if(gameInfo.holdingsWidth && 
6262                 (WhiteOnMove(currentMove) 
6263                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6264                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6265             // click in right holdings, for determining promotion piece
6266             ChessSquare p = boards[currentMove][y][x];
6267             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6268             if(p != EmptySquare) {
6269                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6270                 fromX = fromY = -1;
6271                 return;
6272             }
6273         }
6274         DrawPosition(FALSE, boards[currentMove]);
6275         return;
6276     }
6277
6278     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6279     if(clickType == Press
6280             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6281               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6282               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6283         return;
6284
6285     autoQueen = appData.alwaysPromoteToQueen;
6286
6287     if (fromX == -1) {
6288       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6289         if (clickType == Press) {
6290             /* First square */
6291             if (OKToStartUserMove(x, y)) {
6292                 fromX = x;
6293                 fromY = y;
6294                 second = 0;
6295                 MarkTargetSquares(0);
6296                 DragPieceBegin(xPix, yPix); dragging = 1;
6297                 if (appData.highlightDragging) {
6298                     SetHighlights(x, y, -1, -1);
6299                 }
6300             }
6301         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6302             DragPieceEnd(xPix, yPix); dragging = 0;
6303             DrawPosition(FALSE, NULL);
6304         }
6305         return;
6306       }
6307     }
6308
6309     /* fromX != -1 */
6310     if (clickType == Press && gameMode != EditPosition) {
6311         ChessSquare fromP;
6312         ChessSquare toP;
6313         int frc;
6314
6315         // ignore off-board to clicks
6316         if(y < 0 || x < 0) return;
6317
6318         /* Check if clicking again on the same color piece */
6319         fromP = boards[currentMove][fromY][fromX];
6320         toP = boards[currentMove][y][x];
6321         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6322         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6323              WhitePawn <= toP && toP <= WhiteKing &&
6324              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6325              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6326             (BlackPawn <= fromP && fromP <= BlackKing && 
6327              BlackPawn <= toP && toP <= BlackKing &&
6328              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6329              !(fromP == BlackKing && toP == BlackRook && frc))) {
6330             /* Clicked again on same color piece -- changed his mind */
6331             second = (x == fromX && y == fromY);
6332            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6333             if (appData.highlightDragging) {
6334                 SetHighlights(x, y, -1, -1);
6335             } else {
6336                 ClearHighlights();
6337             }
6338             if (OKToStartUserMove(x, y)) {
6339                 fromX = x;
6340                 fromY = y; dragging = 1;
6341                 MarkTargetSquares(0);
6342                 DragPieceBegin(xPix, yPix);
6343             }
6344             return;
6345            }
6346         }
6347         // ignore clicks on holdings
6348         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6349     }
6350
6351     if (clickType == Release && x == fromX && y == fromY) {
6352         DragPieceEnd(xPix, yPix); dragging = 0;
6353         if (appData.animateDragging) {
6354             /* Undo animation damage if any */
6355             DrawPosition(FALSE, NULL);
6356         }
6357         if (second) {
6358             /* Second up/down in same square; just abort move */
6359             second = 0;
6360             fromX = fromY = -1;
6361             ClearHighlights();
6362             gotPremove = 0;
6363             ClearPremoveHighlights();
6364         } else {
6365             /* First upclick in same square; start click-click mode */
6366             SetHighlights(x, y, -1, -1);
6367         }
6368         return;
6369     }
6370
6371     /* we now have a different from- and (possibly off-board) to-square */
6372     /* Completed move */
6373     toX = x;
6374     toY = y;
6375     saveAnimate = appData.animate;
6376     if (clickType == Press) {
6377         /* Finish clickclick move */
6378         if (appData.animate || appData.highlightLastMove) {
6379             SetHighlights(fromX, fromY, toX, toY);
6380         } else {
6381             ClearHighlights();
6382         }
6383     } else {
6384         /* Finish drag move */
6385         if (appData.highlightLastMove) {
6386             SetHighlights(fromX, fromY, toX, toY);
6387         } else {
6388             ClearHighlights();
6389         }
6390         DragPieceEnd(xPix, yPix); dragging = 0;
6391         /* Don't animate move and drag both */
6392         appData.animate = FALSE;
6393     }
6394
6395     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6396     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6397         ChessSquare piece = boards[currentMove][fromY][fromX];
6398         if(gameMode == EditPosition && piece != EmptySquare &&
6399            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6400             int n;
6401              
6402             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6403                 n = PieceToNumber(piece - (int)BlackPawn);
6404                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6405                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6406                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6407             } else
6408             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6409                 n = PieceToNumber(piece);
6410                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6411                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6412                 boards[currentMove][n][BOARD_WIDTH-2]++;
6413             }
6414             boards[currentMove][fromY][fromX] = EmptySquare;
6415         }
6416         ClearHighlights();
6417         fromX = fromY = -1;
6418         DrawPosition(TRUE, boards[currentMove]);
6419         return;
6420     }
6421
6422     // off-board moves should not be highlighted
6423     if(x < 0 || x < 0) ClearHighlights();
6424
6425     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6426         SetHighlights(fromX, fromY, toX, toY);
6427         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6428             // [HGM] super: promotion to captured piece selected from holdings
6429             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6430             promotionChoice = TRUE;
6431             // kludge follows to temporarily execute move on display, without promoting yet
6432             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6433             boards[currentMove][toY][toX] = p;
6434             DrawPosition(FALSE, boards[currentMove]);
6435             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6436             boards[currentMove][toY][toX] = q;
6437             DisplayMessage("Click in holdings to choose piece", "");
6438             return;
6439         }
6440         PromotionPopUp();
6441     } else {
6442         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6443         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6444         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6445         fromX = fromY = -1;
6446     }
6447     appData.animate = saveAnimate;
6448     if (appData.animate || appData.animateDragging) {
6449         /* Undo animation damage if needed */
6450         DrawPosition(FALSE, NULL);
6451     }
6452 }
6453
6454 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6455 {   // front-end-free part taken out of PieceMenuPopup
6456     int whichMenu; int xSqr, ySqr;
6457
6458     if(seekGraphUp) { // [HGM] seekgraph
6459         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6460         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6461         return -2;
6462     }
6463
6464     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6465          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6466         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6467         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6468         if(action == Press)   {
6469             originalFlip = flipView;
6470             flipView = !flipView; // temporarily flip board to see game from partners perspective
6471             DrawPosition(TRUE, partnerBoard);
6472             DisplayMessage(partnerStatus, "");
6473             partnerUp = TRUE;
6474         } else if(action == Release) {
6475             flipView = originalFlip;
6476             DrawPosition(TRUE, boards[currentMove]);
6477             partnerUp = FALSE;
6478         }
6479         return -2;
6480     }
6481
6482     xSqr = EventToSquare(x, BOARD_WIDTH);
6483     ySqr = EventToSquare(y, BOARD_HEIGHT);
6484     if (action == Release) UnLoadPV(); // [HGM] pv
6485     if (action != Press) return -2; // return code to be ignored
6486     switch (gameMode) {
6487       case IcsExamining:
6488         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6489       case EditPosition:
6490         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6491         if (xSqr < 0 || ySqr < 0) return -1;\r
6492         whichMenu = 0; // edit-position menu
6493         break;
6494       case IcsObserving:
6495         if(!appData.icsEngineAnalyze) return -1;
6496       case IcsPlayingWhite:
6497       case IcsPlayingBlack:
6498         if(!appData.zippyPlay) goto noZip;
6499       case AnalyzeMode:
6500       case AnalyzeFile:
6501       case MachinePlaysWhite:
6502       case MachinePlaysBlack:
6503       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6504         if (!appData.dropMenu) {
6505           LoadPV(x, y);
6506           return 2; // flag front-end to grab mouse events
6507         }
6508         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6509            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6510       case EditGame:
6511       noZip:
6512         if (xSqr < 0 || ySqr < 0) return -1;
6513         if (!appData.dropMenu || appData.testLegality &&
6514             gameInfo.variant != VariantBughouse &&
6515             gameInfo.variant != VariantCrazyhouse) return -1;
6516         whichMenu = 1; // drop menu
6517         break;
6518       default:
6519         return -1;
6520     }
6521
6522     if (((*fromX = xSqr) < 0) ||
6523         ((*fromY = ySqr) < 0)) {
6524         *fromX = *fromY = -1;
6525         return -1;
6526     }
6527     if (flipView)
6528       *fromX = BOARD_WIDTH - 1 - *fromX;
6529     else
6530       *fromY = BOARD_HEIGHT - 1 - *fromY;
6531
6532     return whichMenu;
6533 }
6534
6535 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6536 {
6537 //    char * hint = lastHint;
6538     FrontEndProgramStats stats;
6539
6540     stats.which = cps == &first ? 0 : 1;
6541     stats.depth = cpstats->depth;
6542     stats.nodes = cpstats->nodes;
6543     stats.score = cpstats->score;
6544     stats.time = cpstats->time;
6545     stats.pv = cpstats->movelist;
6546     stats.hint = lastHint;
6547     stats.an_move_index = 0;
6548     stats.an_move_count = 0;
6549
6550     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6551         stats.hint = cpstats->move_name;
6552         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6553         stats.an_move_count = cpstats->nr_moves;
6554     }
6555
6556     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6557
6558     SetProgramStats( &stats );
6559 }
6560
6561 void
6562 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6563 {       // count all piece types
6564         int p, f, r;
6565         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6566         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6567         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6568                 p = board[r][f];
6569                 pCnt[p]++;
6570                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6571                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6572                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6573                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6574                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6575                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6576         }
6577 }
6578
6579 int
6580 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6581 {
6582         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6583         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6584                    
6585         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6586         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6587         if(myPawns == 2 && nMine == 3) // KPP
6588             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6589         if(myPawns == 1 && nMine == 2) // KP
6590             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6591         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6592             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6593         if(myPawns) return FALSE;
6594         if(pCnt[WhiteRook+side])
6595             return pCnt[BlackRook-side] || 
6596                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6597                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6598                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6599         if(pCnt[WhiteCannon+side]) {
6600             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6601             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6602         }
6603         if(pCnt[WhiteKnight+side])
6604             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6605         return FALSE;
6606 }
6607
6608 int
6609 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6610 {
6611         VariantClass v = gameInfo.variant;
6612
6613         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6614         if(v == VariantShatranj) return TRUE; // always winnable through baring
6615         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6616         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6617
6618         if(v == VariantXiangqi) {
6619                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6620
6621                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6622                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6623                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6624                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6625                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6626                 if(stale) // we have at least one last-rank P plus perhaps C
6627                     return majors // KPKX
6628                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6629                 else // KCA*E*
6630                     return pCnt[WhiteFerz+side] // KCAK
6631                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6632                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6633                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6634
6635         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6636                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6637                 
6638                 if(nMine == 1) return FALSE; // bare King
6639                 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
6640                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6641                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6642                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6643                 if(pCnt[WhiteKnight+side])
6644                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6645                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6646                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6647                 if(nBishops)
6648                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6649                 if(pCnt[WhiteAlfil+side])
6650                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6651                 if(pCnt[WhiteWazir+side])
6652                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6653         }
6654
6655         return TRUE;
6656 }
6657
6658 int
6659 Adjudicate(ChessProgramState *cps)
6660 {       // [HGM] some adjudications useful with buggy engines
6661         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6662         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6663         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6664         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6665         int k, count = 0; static int bare = 1;
6666         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6667         Boolean canAdjudicate = !appData.icsActive;
6668
6669         // most tests only when we understand the game, i.e. legality-checking on
6670             if( appData.testLegality )
6671             {   /* [HGM] Some more adjudications for obstinate engines */
6672                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6673                 static int moveCount = 6;
6674                 ChessMove result;
6675                 char *reason = NULL;
6676
6677                 /* Count what is on board. */
6678                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6679
6680                 /* Some material-based adjudications that have to be made before stalemate test */
6681                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6682                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6683                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6684                      if(canAdjudicate && appData.checkMates) {
6685                          if(engineOpponent)
6686                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6687                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6688                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6689                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6690                          return 1;
6691                      }
6692                 }
6693
6694                 /* Bare King in Shatranj (loses) or Losers (wins) */
6695                 if( nrW == 1 || nrB == 1) {
6696                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6697                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6698                      if(canAdjudicate && appData.checkMates) {
6699                          if(engineOpponent)
6700                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6701                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6702                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6703                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6704                          return 1;
6705                      }
6706                   } else
6707                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6708                   {    /* bare King */
6709                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6710                         if(canAdjudicate && appData.checkMates) {
6711                             /* but only adjudicate if adjudication enabled */
6712                             if(engineOpponent)
6713                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6714                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6715                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6716                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6717                             return 1;
6718                         }
6719                   }
6720                 } else bare = 1;
6721
6722
6723             // don't wait for engine to announce game end if we can judge ourselves
6724             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6725               case MT_CHECK:
6726                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6727                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6728                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6729                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6730                             checkCnt++;
6731                         if(checkCnt >= 2) {
6732                             reason = "Xboard adjudication: 3rd check";
6733                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6734                             break;
6735                         }
6736                     }
6737                 }
6738               case MT_NONE:
6739               default:
6740                 break;
6741               case MT_STALEMATE:
6742               case MT_STAINMATE:
6743                 reason = "Xboard adjudication: Stalemate";
6744                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6745                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6746                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6747                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6748                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6749                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6750                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6751                                                                         EP_CHECKMATE : EP_WINS);
6752                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6753                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6754                 }
6755                 break;
6756               case MT_CHECKMATE:
6757                 reason = "Xboard adjudication: Checkmate";
6758                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6759                 break;
6760             }
6761
6762                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6763                     case EP_STALEMATE:
6764                         result = GameIsDrawn; break;
6765                     case EP_CHECKMATE:
6766                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6767                     case EP_WINS:
6768                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6769                     default:
6770                         result = (ChessMove) 0;
6771                 }
6772                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6773                     if(engineOpponent)
6774                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6775                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6776                     GameEnds( result, reason, GE_XBOARD );
6777                     return 1;
6778                 }
6779
6780                 /* Next absolutely insufficient mating material. */
6781                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6782                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6783                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6784
6785                      /* always flag draws, for judging claims */
6786                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6787
6788                      if(canAdjudicate && appData.materialDraws) {
6789                          /* but only adjudicate them if adjudication enabled */
6790                          if(engineOpponent) {
6791                            SendToProgram("force\n", engineOpponent); // suppress reply
6792                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6793                          }
6794                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6796                          return 1;
6797                      }
6798                 }
6799
6800                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6801                 if(gameInfo.variant == VariantXiangqi ?
6802                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6803                  : nrW + nrB == 4 && 
6804                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6805                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6806                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6807                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6808                    ) ) {
6809                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6810                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6811                           if(engineOpponent) {
6812                             SendToProgram("force\n", engineOpponent); // suppress reply
6813                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6814                           }
6815                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6816                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6817                           return 1;
6818                      }
6819                 } else moveCount = 6;
6820             }
6821           
6822         if (appData.debugMode) { int i;
6823             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6824                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6825                     appData.drawRepeats);
6826             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6827               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6828             
6829         }
6830
6831         // Repetition draws and 50-move rule can be applied independently of legality testing
6832
6833                 /* Check for rep-draws */
6834                 count = 0;
6835                 for(k = forwardMostMove-2;
6836                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6837                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6838                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6839                     k-=2)
6840                 {   int rights=0;
6841                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6842                         /* compare castling rights */
6843                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6844                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6845                                 rights++; /* King lost rights, while rook still had them */
6846                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6847                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6848                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6849                                    rights++; /* but at least one rook lost them */
6850                         }
6851                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6852                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6853                                 rights++; 
6854                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6855                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6856                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6857                                    rights++;
6858                         }
6859                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6860                             && appData.drawRepeats > 1) {
6861                              /* adjudicate after user-specified nr of repeats */
6862                              int result = GameIsDrawn;
6863                              char *details = "XBoard adjudication: repetition draw";
6864                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6865                                 // [HGM] xiangqi: check for forbidden perpetuals
6866                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6867                                 for(m=forwardMostMove; m>k; m-=2) {
6868                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6869                                         ourPerpetual = 0; // the current mover did not always check
6870                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6871                                         hisPerpetual = 0; // the opponent did not always check
6872                                 }
6873                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6874                                                                         ourPerpetual, hisPerpetual);
6875                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6876                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6877                                     details = "Xboard adjudication: perpetual checking";
6878                                 } else
6879                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6880                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6881                                 } else
6882                                 // Now check for perpetual chases
6883                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6884                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6885                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6886                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6887                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6888                                         details = "Xboard adjudication: perpetual chasing";
6889                                     } else
6890                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6891                                         break; // Abort repetition-checking loop.
6892                                 }
6893                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6894                              }
6895                              if(engineOpponent) {
6896                                SendToProgram("force\n", engineOpponent); // suppress reply
6897                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6898                              }
6899                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6900                              GameEnds( result, details, GE_XBOARD );
6901                              return 1;
6902                         }
6903                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6904                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6905                     }
6906                 }
6907
6908                 /* Now we test for 50-move draws. Determine ply count */
6909                 count = forwardMostMove;
6910                 /* look for last irreversble move */
6911                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6912                     count--;
6913                 /* if we hit starting position, add initial plies */
6914                 if( count == backwardMostMove )
6915                     count -= initialRulePlies;
6916                 count = forwardMostMove - count; 
6917                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6918                         // adjust reversible move counter for checks in Xiangqi
6919                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6920                         if(i < backwardMostMove) i = backwardMostMove;
6921                         while(i <= forwardMostMove) {
6922                                 lastCheck = inCheck; // check evasion does not count
6923                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6924                                 if(inCheck || lastCheck) count--; // check does not count
6925                                 i++;
6926                         }
6927                 }
6928                 if( count >= 100)
6929                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6930                          /* this is used to judge if draw claims are legal */
6931                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6932                          if(engineOpponent) {
6933                            SendToProgram("force\n", engineOpponent); // suppress reply
6934                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6935                          }
6936                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6937                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6938                          return 1;
6939                 }
6940
6941                 /* if draw offer is pending, treat it as a draw claim
6942                  * when draw condition present, to allow engines a way to
6943                  * claim draws before making their move to avoid a race
6944                  * condition occurring after their move
6945                  */
6946                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6947                          char *p = NULL;
6948                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6949                              p = "Draw claim: 50-move rule";
6950                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6951                              p = "Draw claim: 3-fold repetition";
6952                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6953                              p = "Draw claim: insufficient mating material";
6954                          if( p != NULL && canAdjudicate) {
6955                              if(engineOpponent) {
6956                                SendToProgram("force\n", engineOpponent); // suppress reply
6957                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6958                              }
6959                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6960                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6961                              return 1;
6962                          }
6963                 }
6964
6965                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6966                     if(engineOpponent) {
6967                       SendToProgram("force\n", engineOpponent); // suppress reply
6968                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6969                     }
6970                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6971                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6972                     return 1;
6973                 }
6974         return 0;
6975 }
6976
6977 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6978 {   // [HGM] book: this routine intercepts moves to simulate book replies
6979     char *bookHit = NULL;
6980
6981     //first determine if the incoming move brings opponent into his book
6982     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6983         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6984     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6985     if(bookHit != NULL && !cps->bookSuspend) {
6986         // make sure opponent is not going to reply after receiving move to book position
6987         SendToProgram("force\n", cps);
6988         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6989     }
6990     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6991     // now arrange restart after book miss
6992     if(bookHit) {
6993         // after a book hit we never send 'go', and the code after the call to this routine
6994         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6995         char buf[MSG_SIZ];
6996         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6997         SendToProgram(buf, cps);
6998         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6999     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7000         SendToProgram("go\n", cps);
7001         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7002     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7003         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7004             SendToProgram("go\n", cps); 
7005         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7006     }
7007     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7008 }
7009
7010 char *savedMessage;
7011 ChessProgramState *savedState;
7012 void DeferredBookMove(void)
7013 {
7014         if(savedState->lastPing != savedState->lastPong)
7015                     ScheduleDelayedEvent(DeferredBookMove, 10);
7016         else
7017         HandleMachineMove(savedMessage, savedState);
7018 }
7019
7020 void
7021 HandleMachineMove(message, cps)
7022      char *message;
7023      ChessProgramState *cps;
7024 {
7025     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7026     char realname[MSG_SIZ];
7027     int fromX, fromY, toX, toY;
7028     ChessMove moveType;
7029     char promoChar;
7030     char *p;
7031     int machineWhite;
7032     char *bookHit;
7033
7034     cps->userError = 0;
7035
7036 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7037     /*
7038      * Kludge to ignore BEL characters
7039      */
7040     while (*message == '\007') message++;
7041
7042     /*
7043      * [HGM] engine debug message: ignore lines starting with '#' character
7044      */
7045     if(cps->debug && *message == '#') return;
7046
7047     /*
7048      * Look for book output
7049      */
7050     if (cps == &first && bookRequested) {
7051         if (message[0] == '\t' || message[0] == ' ') {
7052             /* Part of the book output is here; append it */
7053             strcat(bookOutput, message);
7054             strcat(bookOutput, "  \n");
7055             return;
7056         } else if (bookOutput[0] != NULLCHAR) {
7057             /* All of book output has arrived; display it */
7058             char *p = bookOutput;
7059             while (*p != NULLCHAR) {
7060                 if (*p == '\t') *p = ' ';
7061                 p++;
7062             }
7063             DisplayInformation(bookOutput);
7064             bookRequested = FALSE;
7065             /* Fall through to parse the current output */
7066         }
7067     }
7068
7069     /*
7070      * Look for machine move.
7071      */
7072     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7073         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7074     {
7075         /* This method is only useful on engines that support ping */
7076         if (cps->lastPing != cps->lastPong) {
7077           if (gameMode == BeginningOfGame) {
7078             /* Extra move from before last new; ignore */
7079             if (appData.debugMode) {
7080                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7081             }
7082           } else {
7083             if (appData.debugMode) {
7084                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7085                         cps->which, gameMode);
7086             }
7087
7088             SendToProgram("undo\n", cps);
7089           }
7090           return;
7091         }
7092
7093         switch (gameMode) {
7094           case BeginningOfGame:
7095             /* Extra move from before last reset; ignore */
7096             if (appData.debugMode) {
7097                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7098             }
7099             return;
7100
7101           case EndOfGame:
7102           case IcsIdle:
7103           default:
7104             /* Extra move after we tried to stop.  The mode test is
7105                not a reliable way of detecting this problem, but it's
7106                the best we can do on engines that don't support ping.
7107             */
7108             if (appData.debugMode) {
7109                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7110                         cps->which, gameMode);
7111             }
7112             SendToProgram("undo\n", cps);
7113             return;
7114
7115           case MachinePlaysWhite:
7116           case IcsPlayingWhite:
7117             machineWhite = TRUE;
7118             break;
7119
7120           case MachinePlaysBlack:
7121           case IcsPlayingBlack:
7122             machineWhite = FALSE;
7123             break;
7124
7125           case TwoMachinesPlay:
7126             machineWhite = (cps->twoMachinesColor[0] == 'w');
7127             break;
7128         }
7129         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7130             if (appData.debugMode) {
7131                 fprintf(debugFP,
7132                         "Ignoring move out of turn by %s, gameMode %d"
7133                         ", forwardMost %d\n",
7134                         cps->which, gameMode, forwardMostMove);
7135             }
7136             return;
7137         }
7138
7139     if (appData.debugMode) { int f = forwardMostMove;
7140         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7141                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7142                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7143     }
7144         if(cps->alphaRank) AlphaRank(machineMove, 4);
7145         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7146                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7147             /* Machine move could not be parsed; ignore it. */
7148             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7149                     machineMove, cps->which);
7150             DisplayError(buf1, 0);
7151             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7152                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7153             if (gameMode == TwoMachinesPlay) {
7154               GameEnds(machineWhite ? BlackWins : WhiteWins,
7155                        buf1, GE_XBOARD);
7156             }
7157             return;
7158         }
7159
7160         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7161         /* So we have to redo legality test with true e.p. status here,  */
7162         /* to make sure an illegal e.p. capture does not slip through,   */
7163         /* to cause a forfeit on a justified illegal-move complaint      */
7164         /* of the opponent.                                              */
7165         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7166            ChessMove moveType;
7167            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7168                              fromY, fromX, toY, toX, promoChar);
7169             if (appData.debugMode) {
7170                 int i;
7171                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7172                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7173                 fprintf(debugFP, "castling rights\n");
7174             }
7175             if(moveType == IllegalMove) {
7176                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7177                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7178                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7179                            buf1, GE_XBOARD);
7180                 return;
7181            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7182            /* [HGM] Kludge to handle engines that send FRC-style castling
7183               when they shouldn't (like TSCP-Gothic) */
7184            switch(moveType) {
7185              case WhiteASideCastleFR:
7186              case BlackASideCastleFR:
7187                toX+=2;
7188                currentMoveString[2]++;
7189                break;
7190              case WhiteHSideCastleFR:
7191              case BlackHSideCastleFR:
7192                toX--;
7193                currentMoveString[2]--;
7194                break;
7195              default: ; // nothing to do, but suppresses warning of pedantic compilers
7196            }
7197         }
7198         hintRequested = FALSE;
7199         lastHint[0] = NULLCHAR;
7200         bookRequested = FALSE;
7201         /* Program may be pondering now */
7202         cps->maybeThinking = TRUE;
7203         if (cps->sendTime == 2) cps->sendTime = 1;
7204         if (cps->offeredDraw) cps->offeredDraw--;
7205
7206         /* currentMoveString is set as a side-effect of ParseOneMove */
7207         strcpy(machineMove, currentMoveString);
7208         strcat(machineMove, "\n");
7209         strcpy(moveList[forwardMostMove], machineMove);
7210
7211         /* [AS] Save move info*/
7212         pvInfoList[ forwardMostMove ].score = programStats.score;
7213         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7214         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7215
7216         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7217
7218         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7219         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7220             int count = 0;
7221
7222             while( count < adjudicateLossPlies ) {
7223                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7224
7225                 if( count & 1 ) {
7226                     score = -score; /* Flip score for winning side */
7227                 }
7228
7229                 if( score > adjudicateLossThreshold ) {
7230                     break;
7231                 }
7232
7233                 count++;
7234             }
7235
7236             if( count >= adjudicateLossPlies ) {
7237                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7238
7239                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7240                     "Xboard adjudication", 
7241                     GE_XBOARD );
7242
7243                 return;
7244             }
7245         }
7246
7247         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7248
7249 #if ZIPPY
7250         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7251             first.initDone) {
7252           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7253                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7254                 SendToICS("draw ");
7255                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7256           }
7257           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7258           ics_user_moved = 1;
7259           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7260                 char buf[3*MSG_SIZ];
7261
7262                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7263                         programStats.score / 100.,
7264                         programStats.depth,
7265                         programStats.time / 100.,
7266                         (unsigned int)programStats.nodes,
7267                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7268                         programStats.movelist);
7269                 SendToICS(buf);
7270 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7271           }
7272         }
7273 #endif
7274
7275         /* [AS] Clear stats for next move */
7276         ClearProgramStats();
7277         thinkOutput[0] = NULLCHAR;
7278         hiddenThinkOutputState = 0;
7279
7280         bookHit = NULL;
7281         if (gameMode == TwoMachinesPlay) {
7282             /* [HGM] relaying draw offers moved to after reception of move */
7283             /* and interpreting offer as claim if it brings draw condition */
7284             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7285                 SendToProgram("draw\n", cps->other);
7286             }
7287             if (cps->other->sendTime) {
7288                 SendTimeRemaining(cps->other,
7289                                   cps->other->twoMachinesColor[0] == 'w');
7290             }
7291             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7292             if (firstMove && !bookHit) {
7293                 firstMove = FALSE;
7294                 if (cps->other->useColors) {
7295                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7296                 }
7297                 SendToProgram("go\n", cps->other);
7298             }
7299             cps->other->maybeThinking = TRUE;
7300         }
7301
7302         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7303         
7304         if (!pausing && appData.ringBellAfterMoves) {
7305             RingBell();
7306         }
7307
7308         /* 
7309          * Reenable menu items that were disabled while
7310          * machine was thinking
7311          */
7312         if (gameMode != TwoMachinesPlay)
7313             SetUserThinkingEnables();
7314
7315         // [HGM] book: after book hit opponent has received move and is now in force mode
7316         // force the book reply into it, and then fake that it outputted this move by jumping
7317         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7318         if(bookHit) {
7319                 static char bookMove[MSG_SIZ]; // a bit generous?
7320
7321                 strcpy(bookMove, "move ");
7322                 strcat(bookMove, bookHit);
7323                 message = bookMove;
7324                 cps = cps->other;
7325                 programStats.nodes = programStats.depth = programStats.time = 
7326                 programStats.score = programStats.got_only_move = 0;
7327                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7328
7329                 if(cps->lastPing != cps->lastPong) {
7330                     savedMessage = message; // args for deferred call
7331                     savedState = cps;
7332                     ScheduleDelayedEvent(DeferredBookMove, 10);
7333                     return;
7334                 }
7335                 goto FakeBookMove;
7336         }
7337
7338         return;
7339     }
7340
7341     /* Set special modes for chess engines.  Later something general
7342      *  could be added here; for now there is just one kludge feature,
7343      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7344      *  when "xboard" is given as an interactive command.
7345      */
7346     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7347         cps->useSigint = FALSE;
7348         cps->useSigterm = FALSE;
7349     }
7350     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7351       ParseFeatures(message+8, cps);
7352       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7353     }
7354
7355     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7356      * want this, I was asked to put it in, and obliged.
7357      */
7358     if (!strncmp(message, "setboard ", 9)) {
7359         Board initial_position;
7360
7361         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7362
7363         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7364             DisplayError(_("Bad FEN received from engine"), 0);
7365             return ;
7366         } else {
7367            Reset(TRUE, FALSE);
7368            CopyBoard(boards[0], initial_position);
7369            initialRulePlies = FENrulePlies;
7370            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7371            else gameMode = MachinePlaysBlack;                 
7372            DrawPosition(FALSE, boards[currentMove]);
7373         }
7374         return;
7375     }
7376
7377     /*
7378      * Look for communication commands
7379      */
7380     if (!strncmp(message, "telluser ", 9)) {
7381         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7382         DisplayNote(message + 9);
7383         return;
7384     }
7385     if (!strncmp(message, "tellusererror ", 14)) {
7386         cps->userError = 1;
7387         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7388         DisplayError(message + 14, 0);
7389         return;
7390     }
7391     if (!strncmp(message, "tellopponent ", 13)) {
7392       if (appData.icsActive) {
7393         if (loggedOn) {
7394           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7395           SendToICS(buf1);
7396         }
7397       } else {
7398         DisplayNote(message + 13);
7399       }
7400       return;
7401     }
7402     if (!strncmp(message, "tellothers ", 11)) {
7403       if (appData.icsActive) {
7404         if (loggedOn) {
7405           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7406           SendToICS(buf1);
7407         }
7408       }
7409       return;
7410     }
7411     if (!strncmp(message, "tellall ", 8)) {
7412       if (appData.icsActive) {
7413         if (loggedOn) {
7414           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7415           SendToICS(buf1);
7416         }
7417       } else {
7418         DisplayNote(message + 8);
7419       }
7420       return;
7421     }
7422     if (strncmp(message, "warning", 7) == 0) {
7423         /* Undocumented feature, use tellusererror in new code */
7424         DisplayError(message, 0);
7425         return;
7426     }
7427     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7428         strcpy(realname, cps->tidy);
7429         strcat(realname, " query");
7430         AskQuestion(realname, buf2, buf1, cps->pr);
7431         return;
7432     }
7433     /* Commands from the engine directly to ICS.  We don't allow these to be 
7434      *  sent until we are logged on. Crafty kibitzes have been known to 
7435      *  interfere with the login process.
7436      */
7437     if (loggedOn) {
7438         if (!strncmp(message, "tellics ", 8)) {
7439             SendToICS(message + 8);
7440             SendToICS("\n");
7441             return;
7442         }
7443         if (!strncmp(message, "tellicsnoalias ", 15)) {
7444             SendToICS(ics_prefix);
7445             SendToICS(message + 15);
7446             SendToICS("\n");
7447             return;
7448         }
7449         /* The following are for backward compatibility only */
7450         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7451             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7452             SendToICS(ics_prefix);
7453             SendToICS(message);
7454             SendToICS("\n");
7455             return;
7456         }
7457     }
7458     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7459         return;
7460     }
7461     /*
7462      * If the move is illegal, cancel it and redraw the board.
7463      * Also deal with other error cases.  Matching is rather loose
7464      * here to accommodate engines written before the spec.
7465      */
7466     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7467         strncmp(message, "Error", 5) == 0) {
7468         if (StrStr(message, "name") || 
7469             StrStr(message, "rating") || StrStr(message, "?") ||
7470             StrStr(message, "result") || StrStr(message, "board") ||
7471             StrStr(message, "bk") || StrStr(message, "computer") ||
7472             StrStr(message, "variant") || StrStr(message, "hint") ||
7473             StrStr(message, "random") || StrStr(message, "depth") ||
7474             StrStr(message, "accepted")) {
7475             return;
7476         }
7477         if (StrStr(message, "protover")) {
7478           /* Program is responding to input, so it's apparently done
7479              initializing, and this error message indicates it is
7480              protocol version 1.  So we don't need to wait any longer
7481              for it to initialize and send feature commands. */
7482           FeatureDone(cps, 1);
7483           cps->protocolVersion = 1;
7484           return;
7485         }
7486         cps->maybeThinking = FALSE;
7487
7488         if (StrStr(message, "draw")) {
7489             /* Program doesn't have "draw" command */
7490             cps->sendDrawOffers = 0;
7491             return;
7492         }
7493         if (cps->sendTime != 1 &&
7494             (StrStr(message, "time") || StrStr(message, "otim"))) {
7495           /* Program apparently doesn't have "time" or "otim" command */
7496           cps->sendTime = 0;
7497           return;
7498         }
7499         if (StrStr(message, "analyze")) {
7500             cps->analysisSupport = FALSE;
7501             cps->analyzing = FALSE;
7502             Reset(FALSE, TRUE);
7503             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7504             DisplayError(buf2, 0);
7505             return;
7506         }
7507         if (StrStr(message, "(no matching move)st")) {
7508           /* Special kludge for GNU Chess 4 only */
7509           cps->stKludge = TRUE;
7510           SendTimeControl(cps, movesPerSession, timeControl,
7511                           timeIncrement, appData.searchDepth,
7512                           searchTime);
7513           return;
7514         }
7515         if (StrStr(message, "(no matching move)sd")) {
7516           /* Special kludge for GNU Chess 4 only */
7517           cps->sdKludge = TRUE;
7518           SendTimeControl(cps, movesPerSession, timeControl,
7519                           timeIncrement, appData.searchDepth,
7520                           searchTime);
7521           return;
7522         }
7523         if (!StrStr(message, "llegal")) {
7524             return;
7525         }
7526         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7527             gameMode == IcsIdle) return;
7528         if (forwardMostMove <= backwardMostMove) return;
7529         if (pausing) PauseEvent();
7530       if(appData.forceIllegal) {
7531             // [HGM] illegal: machine refused move; force position after move into it
7532           SendToProgram("force\n", cps);
7533           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7534                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7535                 // when black is to move, while there might be nothing on a2 or black
7536                 // might already have the move. So send the board as if white has the move.
7537                 // But first we must change the stm of the engine, as it refused the last move
7538                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7539                 if(WhiteOnMove(forwardMostMove)) {
7540                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7541                     SendBoard(cps, forwardMostMove); // kludgeless board
7542                 } else {
7543                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7544                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7545                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7546                 }
7547           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7548             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7549                  gameMode == TwoMachinesPlay)
7550               SendToProgram("go\n", cps);
7551             return;
7552       } else
7553         if (gameMode == PlayFromGameFile) {
7554             /* Stop reading this game file */
7555             gameMode = EditGame;
7556             ModeHighlight();
7557         }
7558         currentMove = forwardMostMove-1;
7559         DisplayMove(currentMove-1); /* before DisplayMoveError */
7560         SwitchClocks(forwardMostMove-1); // [HGM] race
7561         DisplayBothClocks();
7562         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7563                 parseList[currentMove], cps->which);
7564         DisplayMoveError(buf1);
7565         DrawPosition(FALSE, boards[currentMove]);
7566
7567         /* [HGM] illegal-move claim should forfeit game when Xboard */
7568         /* only passes fully legal moves                            */
7569         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7570             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7571                                 "False illegal-move claim", GE_XBOARD );
7572         }
7573         return;
7574     }
7575     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7576         /* Program has a broken "time" command that
7577            outputs a string not ending in newline.
7578            Don't use it. */
7579         cps->sendTime = 0;
7580     }
7581     
7582     /*
7583      * If chess program startup fails, exit with an error message.
7584      * Attempts to recover here are futile.
7585      */
7586     if ((StrStr(message, "unknown host") != NULL)
7587         || (StrStr(message, "No remote directory") != NULL)
7588         || (StrStr(message, "not found") != NULL)
7589         || (StrStr(message, "No such file") != NULL)
7590         || (StrStr(message, "can't alloc") != NULL)
7591         || (StrStr(message, "Permission denied") != NULL)) {
7592
7593         cps->maybeThinking = FALSE;
7594         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7595                 cps->which, cps->program, cps->host, message);
7596         RemoveInputSource(cps->isr);
7597         DisplayFatalError(buf1, 0, 1);
7598         return;
7599     }
7600     
7601     /* 
7602      * Look for hint output
7603      */
7604     if (sscanf(message, "Hint: %s", buf1) == 1) {
7605         if (cps == &first && hintRequested) {
7606             hintRequested = FALSE;
7607             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7608                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7609                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7610                                     PosFlags(forwardMostMove),
7611                                     fromY, fromX, toY, toX, promoChar, buf1);
7612                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7613                 DisplayInformation(buf2);
7614             } else {
7615                 /* Hint move could not be parsed!? */
7616               snprintf(buf2, sizeof(buf2),
7617                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7618                         buf1, cps->which);
7619                 DisplayError(buf2, 0);
7620             }
7621         } else {
7622             strcpy(lastHint, buf1);
7623         }
7624         return;
7625     }
7626
7627     /*
7628      * Ignore other messages if game is not in progress
7629      */
7630     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7631         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7632
7633     /*
7634      * look for win, lose, draw, or draw offer
7635      */
7636     if (strncmp(message, "1-0", 3) == 0) {
7637         char *p, *q, *r = "";
7638         p = strchr(message, '{');
7639         if (p) {
7640             q = strchr(p, '}');
7641             if (q) {
7642                 *q = NULLCHAR;
7643                 r = p + 1;
7644             }
7645         }
7646         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7647         return;
7648     } else if (strncmp(message, "0-1", 3) == 0) {
7649         char *p, *q, *r = "";
7650         p = strchr(message, '{');
7651         if (p) {
7652             q = strchr(p, '}');
7653             if (q) {
7654                 *q = NULLCHAR;
7655                 r = p + 1;
7656             }
7657         }
7658         /* Kludge for Arasan 4.1 bug */
7659         if (strcmp(r, "Black resigns") == 0) {
7660             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7661             return;
7662         }
7663         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7664         return;
7665     } else if (strncmp(message, "1/2", 3) == 0) {
7666         char *p, *q, *r = "";
7667         p = strchr(message, '{');
7668         if (p) {
7669             q = strchr(p, '}');
7670             if (q) {
7671                 *q = NULLCHAR;
7672                 r = p + 1;
7673             }
7674         }
7675             
7676         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7677         return;
7678
7679     } else if (strncmp(message, "White resign", 12) == 0) {
7680         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7681         return;
7682     } else if (strncmp(message, "Black resign", 12) == 0) {
7683         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7684         return;
7685     } else if (strncmp(message, "White matches", 13) == 0 ||
7686                strncmp(message, "Black matches", 13) == 0   ) {
7687         /* [HGM] ignore GNUShogi noises */
7688         return;
7689     } else if (strncmp(message, "White", 5) == 0 &&
7690                message[5] != '(' &&
7691                StrStr(message, "Black") == NULL) {
7692         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7693         return;
7694     } else if (strncmp(message, "Black", 5) == 0 &&
7695                message[5] != '(') {
7696         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7697         return;
7698     } else if (strcmp(message, "resign") == 0 ||
7699                strcmp(message, "computer resigns") == 0) {
7700         switch (gameMode) {
7701           case MachinePlaysBlack:
7702           case IcsPlayingBlack:
7703             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7704             break;
7705           case MachinePlaysWhite:
7706           case IcsPlayingWhite:
7707             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7708             break;
7709           case TwoMachinesPlay:
7710             if (cps->twoMachinesColor[0] == 'w')
7711               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7712             else
7713               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7714             break;
7715           default:
7716             /* can't happen */
7717             break;
7718         }
7719         return;
7720     } else if (strncmp(message, "opponent mates", 14) == 0) {
7721         switch (gameMode) {
7722           case MachinePlaysBlack:
7723           case IcsPlayingBlack:
7724             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7725             break;
7726           case MachinePlaysWhite:
7727           case IcsPlayingWhite:
7728             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7729             break;
7730           case TwoMachinesPlay:
7731             if (cps->twoMachinesColor[0] == 'w')
7732               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7733             else
7734               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7735             break;
7736           default:
7737             /* can't happen */
7738             break;
7739         }
7740         return;
7741     } else if (strncmp(message, "computer mates", 14) == 0) {
7742         switch (gameMode) {
7743           case MachinePlaysBlack:
7744           case IcsPlayingBlack:
7745             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7746             break;
7747           case MachinePlaysWhite:
7748           case IcsPlayingWhite:
7749             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7750             break;
7751           case TwoMachinesPlay:
7752             if (cps->twoMachinesColor[0] == 'w')
7753               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7754             else
7755               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7756             break;
7757           default:
7758             /* can't happen */
7759             break;
7760         }
7761         return;
7762     } else if (strncmp(message, "checkmate", 9) == 0) {
7763         if (WhiteOnMove(forwardMostMove)) {
7764             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7765         } else {
7766             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7767         }
7768         return;
7769     } else if (strstr(message, "Draw") != NULL ||
7770                strstr(message, "game is a draw") != NULL) {
7771         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7772         return;
7773     } else if (strstr(message, "offer") != NULL &&
7774                strstr(message, "draw") != NULL) {
7775 #if ZIPPY
7776         if (appData.zippyPlay && first.initDone) {
7777             /* Relay offer to ICS */
7778             SendToICS(ics_prefix);
7779             SendToICS("draw\n");
7780         }
7781 #endif
7782         cps->offeredDraw = 2; /* valid until this engine moves twice */
7783         if (gameMode == TwoMachinesPlay) {
7784             if (cps->other->offeredDraw) {
7785                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7786             /* [HGM] in two-machine mode we delay relaying draw offer      */
7787             /* until after we also have move, to see if it is really claim */
7788             }
7789         } else if (gameMode == MachinePlaysWhite ||
7790                    gameMode == MachinePlaysBlack) {
7791           if (userOfferedDraw) {
7792             DisplayInformation(_("Machine accepts your draw offer"));
7793             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7794           } else {
7795             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7796           }
7797         }
7798     }
7799
7800     
7801     /*
7802      * Look for thinking output
7803      */
7804     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7805           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7806                                 ) {
7807         int plylev, mvleft, mvtot, curscore, time;
7808         char mvname[MOVE_LEN];
7809         u64 nodes; // [DM]
7810         char plyext;
7811         int ignore = FALSE;
7812         int prefixHint = FALSE;
7813         mvname[0] = NULLCHAR;
7814
7815         switch (gameMode) {
7816           case MachinePlaysBlack:
7817           case IcsPlayingBlack:
7818             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7819             break;
7820           case MachinePlaysWhite:
7821           case IcsPlayingWhite:
7822             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7823             break;
7824           case AnalyzeMode:
7825           case AnalyzeFile:
7826             break;
7827           case IcsObserving: /* [DM] icsEngineAnalyze */
7828             if (!appData.icsEngineAnalyze) ignore = TRUE;
7829             break;
7830           case TwoMachinesPlay:
7831             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7832                 ignore = TRUE;
7833             }
7834             break;
7835           default:
7836             ignore = TRUE;
7837             break;
7838         }
7839
7840         if (!ignore) {
7841             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7842             buf1[0] = NULLCHAR;
7843             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7844                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7845
7846                 if (plyext != ' ' && plyext != '\t') {
7847                     time *= 100;
7848                 }
7849
7850                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7851                 if( cps->scoreIsAbsolute && 
7852                     ( gameMode == MachinePlaysBlack ||
7853                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7854                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7855                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7856                      !WhiteOnMove(currentMove)
7857                     ) )
7858                 {
7859                     curscore = -curscore;
7860                 }
7861
7862
7863                 tempStats.depth = plylev;
7864                 tempStats.nodes = nodes;
7865                 tempStats.time = time;
7866                 tempStats.score = curscore;
7867                 tempStats.got_only_move = 0;
7868
7869                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7870                         int ticklen;
7871
7872                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7873                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7874                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7875                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7876                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7877                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7878                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7879                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7880                 }
7881
7882                 /* Buffer overflow protection */
7883                 if (buf1[0] != NULLCHAR) {
7884                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7885                         && appData.debugMode) {
7886                         fprintf(debugFP,
7887                                 "PV is too long; using the first %u bytes.\n",
7888                                 (unsigned) sizeof(tempStats.movelist) - 1);
7889                     }
7890
7891                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7892                 } else {
7893                     sprintf(tempStats.movelist, " no PV\n");
7894                 }
7895
7896                 if (tempStats.seen_stat) {
7897                     tempStats.ok_to_send = 1;
7898                 }
7899
7900                 if (strchr(tempStats.movelist, '(') != NULL) {
7901                     tempStats.line_is_book = 1;
7902                     tempStats.nr_moves = 0;
7903                     tempStats.moves_left = 0;
7904                 } else {
7905                     tempStats.line_is_book = 0;
7906                 }
7907
7908                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7909                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7910
7911                 SendProgramStatsToFrontend( cps, &tempStats );
7912
7913                 /* 
7914                     [AS] Protect the thinkOutput buffer from overflow... this
7915                     is only useful if buf1 hasn't overflowed first!
7916                 */
7917                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7918                         plylev, 
7919                         (gameMode == TwoMachinesPlay ?
7920                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7921                         ((double) curscore) / 100.0,
7922                         prefixHint ? lastHint : "",
7923                         prefixHint ? " " : "" );
7924
7925                 if( buf1[0] != NULLCHAR ) {
7926                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7927
7928                     if( strlen(buf1) > max_len ) {
7929                         if( appData.debugMode) {
7930                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7931                         }
7932                         buf1[max_len+1] = '\0';
7933                     }
7934
7935                     strcat( thinkOutput, buf1 );
7936                 }
7937
7938                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7939                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7940                     DisplayMove(currentMove - 1);
7941                 }
7942                 return;
7943
7944             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7945                 /* crafty (9.25+) says "(only move) <move>"
7946                  * if there is only 1 legal move
7947                  */
7948                 sscanf(p, "(only move) %s", buf1);
7949                 sprintf(thinkOutput, "%s (only move)", buf1);
7950                 sprintf(programStats.movelist, "%s (only move)", buf1);
7951                 programStats.depth = 1;
7952                 programStats.nr_moves = 1;
7953                 programStats.moves_left = 1;
7954                 programStats.nodes = 1;
7955                 programStats.time = 1;
7956                 programStats.got_only_move = 1;
7957
7958                 /* Not really, but we also use this member to
7959                    mean "line isn't going to change" (Crafty
7960                    isn't searching, so stats won't change) */
7961                 programStats.line_is_book = 1;
7962
7963                 SendProgramStatsToFrontend( cps, &programStats );
7964                 
7965                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7966                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7967                     DisplayMove(currentMove - 1);
7968                 }
7969                 return;
7970             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7971                               &time, &nodes, &plylev, &mvleft,
7972                               &mvtot, mvname) >= 5) {
7973                 /* The stat01: line is from Crafty (9.29+) in response
7974                    to the "." command */
7975                 programStats.seen_stat = 1;
7976                 cps->maybeThinking = TRUE;
7977
7978                 if (programStats.got_only_move || !appData.periodicUpdates)
7979                   return;
7980
7981                 programStats.depth = plylev;
7982                 programStats.time = time;
7983                 programStats.nodes = nodes;
7984                 programStats.moves_left = mvleft;
7985                 programStats.nr_moves = mvtot;
7986                 strcpy(programStats.move_name, mvname);
7987                 programStats.ok_to_send = 1;
7988                 programStats.movelist[0] = '\0';
7989
7990                 SendProgramStatsToFrontend( cps, &programStats );
7991
7992                 return;
7993
7994             } else if (strncmp(message,"++",2) == 0) {
7995                 /* Crafty 9.29+ outputs this */
7996                 programStats.got_fail = 2;
7997                 return;
7998
7999             } else if (strncmp(message,"--",2) == 0) {
8000                 /* Crafty 9.29+ outputs this */
8001                 programStats.got_fail = 1;
8002                 return;
8003
8004             } else if (thinkOutput[0] != NULLCHAR &&
8005                        strncmp(message, "    ", 4) == 0) {
8006                 unsigned message_len;
8007
8008                 p = message;
8009                 while (*p && *p == ' ') p++;
8010
8011                 message_len = strlen( p );
8012
8013                 /* [AS] Avoid buffer overflow */
8014                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8015                     strcat(thinkOutput, " ");
8016                     strcat(thinkOutput, p);
8017                 }
8018
8019                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8020                     strcat(programStats.movelist, " ");
8021                     strcat(programStats.movelist, p);
8022                 }
8023
8024                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8025                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8026                     DisplayMove(currentMove - 1);
8027                 }
8028                 return;
8029             }
8030         }
8031         else {
8032             buf1[0] = NULLCHAR;
8033
8034             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8035                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8036             {
8037                 ChessProgramStats cpstats;
8038
8039                 if (plyext != ' ' && plyext != '\t') {
8040                     time *= 100;
8041                 }
8042
8043                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8044                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8045                     curscore = -curscore;
8046                 }
8047
8048                 cpstats.depth = plylev;
8049                 cpstats.nodes = nodes;
8050                 cpstats.time = time;
8051                 cpstats.score = curscore;
8052                 cpstats.got_only_move = 0;
8053                 cpstats.movelist[0] = '\0';
8054
8055                 if (buf1[0] != NULLCHAR) {
8056                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8057                 }
8058
8059                 cpstats.ok_to_send = 0;
8060                 cpstats.line_is_book = 0;
8061                 cpstats.nr_moves = 0;
8062                 cpstats.moves_left = 0;
8063
8064                 SendProgramStatsToFrontend( cps, &cpstats );
8065             }
8066         }
8067     }
8068 }
8069
8070
8071 /* Parse a game score from the character string "game", and
8072    record it as the history of the current game.  The game
8073    score is NOT assumed to start from the standard position. 
8074    The display is not updated in any way.
8075    */
8076 void
8077 ParseGameHistory(game)
8078      char *game;
8079 {
8080     ChessMove moveType;
8081     int fromX, fromY, toX, toY, boardIndex;
8082     char promoChar;
8083     char *p, *q;
8084     char buf[MSG_SIZ];
8085
8086     if (appData.debugMode)
8087       fprintf(debugFP, "Parsing game history: %s\n", game);
8088
8089     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8090     gameInfo.site = StrSave(appData.icsHost);
8091     gameInfo.date = PGNDate();
8092     gameInfo.round = StrSave("-");
8093
8094     /* Parse out names of players */
8095     while (*game == ' ') game++;
8096     p = buf;
8097     while (*game != ' ') *p++ = *game++;
8098     *p = NULLCHAR;
8099     gameInfo.white = StrSave(buf);
8100     while (*game == ' ') game++;
8101     p = buf;
8102     while (*game != ' ' && *game != '\n') *p++ = *game++;
8103     *p = NULLCHAR;
8104     gameInfo.black = StrSave(buf);
8105
8106     /* Parse moves */
8107     boardIndex = blackPlaysFirst ? 1 : 0;
8108     yynewstr(game);
8109     for (;;) {
8110         yyboardindex = boardIndex;
8111         moveType = (ChessMove) yylex();
8112         switch (moveType) {
8113           case IllegalMove:             /* maybe suicide chess, etc. */
8114   if (appData.debugMode) {
8115     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8116     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8117     setbuf(debugFP, NULL);
8118   }
8119           case WhitePromotionChancellor:
8120           case BlackPromotionChancellor:
8121           case WhitePromotionArchbishop:
8122           case BlackPromotionArchbishop:
8123           case WhitePromotionQueen:
8124           case BlackPromotionQueen:
8125           case WhitePromotionRook:
8126           case BlackPromotionRook:
8127           case WhitePromotionBishop:
8128           case BlackPromotionBishop:
8129           case WhitePromotionKnight:
8130           case BlackPromotionKnight:
8131           case WhitePromotionKing:
8132           case BlackPromotionKing:
8133           case WhiteNonPromotion:
8134           case BlackNonPromotion:
8135           case NormalMove:
8136           case WhiteCapturesEnPassant:
8137           case BlackCapturesEnPassant:
8138           case WhiteKingSideCastle:
8139           case WhiteQueenSideCastle:
8140           case BlackKingSideCastle:
8141           case BlackQueenSideCastle:
8142           case WhiteKingSideCastleWild:
8143           case WhiteQueenSideCastleWild:
8144           case BlackKingSideCastleWild:
8145           case BlackQueenSideCastleWild:
8146           /* PUSH Fabien */
8147           case WhiteHSideCastleFR:
8148           case WhiteASideCastleFR:
8149           case BlackHSideCastleFR:
8150           case BlackASideCastleFR:
8151           /* POP Fabien */
8152             fromX = currentMoveString[0] - AAA;
8153             fromY = currentMoveString[1] - ONE;
8154             toX = currentMoveString[2] - AAA;
8155             toY = currentMoveString[3] - ONE;
8156             promoChar = currentMoveString[4];
8157             break;
8158           case WhiteDrop:
8159           case BlackDrop:
8160             fromX = moveType == WhiteDrop ?
8161               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8162             (int) CharToPiece(ToLower(currentMoveString[0]));
8163             fromY = DROP_RANK;
8164             toX = currentMoveString[2] - AAA;
8165             toY = currentMoveString[3] - ONE;
8166             promoChar = NULLCHAR;
8167             break;
8168           case AmbiguousMove:
8169             /* bug? */
8170             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8171   if (appData.debugMode) {
8172     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8173     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8174     setbuf(debugFP, NULL);
8175   }
8176             DisplayError(buf, 0);
8177             return;
8178           case ImpossibleMove:
8179             /* bug? */
8180             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8181   if (appData.debugMode) {
8182     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8183     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8184     setbuf(debugFP, NULL);
8185   }
8186             DisplayError(buf, 0);
8187             return;
8188           case (ChessMove) 0:   /* end of file */
8189             if (boardIndex < backwardMostMove) {
8190                 /* Oops, gap.  How did that happen? */
8191                 DisplayError(_("Gap in move list"), 0);
8192                 return;
8193             }
8194             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8195             if (boardIndex > forwardMostMove) {
8196                 forwardMostMove = boardIndex;
8197             }
8198             return;
8199           case ElapsedTime:
8200             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8201                 strcat(parseList[boardIndex-1], " ");
8202                 strcat(parseList[boardIndex-1], yy_text);
8203             }
8204             continue;
8205           case Comment:
8206           case PGNTag:
8207           case NAG:
8208           default:
8209             /* ignore */
8210             continue;
8211           case WhiteWins:
8212           case BlackWins:
8213           case GameIsDrawn:
8214           case GameUnfinished:
8215             if (gameMode == IcsExamining) {
8216                 if (boardIndex < backwardMostMove) {
8217                     /* Oops, gap.  How did that happen? */
8218                     return;
8219                 }
8220                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8221                 return;
8222             }
8223             gameInfo.result = moveType;
8224             p = strchr(yy_text, '{');
8225             if (p == NULL) p = strchr(yy_text, '(');
8226             if (p == NULL) {
8227                 p = yy_text;
8228                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8229             } else {
8230                 q = strchr(p, *p == '{' ? '}' : ')');
8231                 if (q != NULL) *q = NULLCHAR;
8232                 p++;
8233             }
8234             gameInfo.resultDetails = StrSave(p);
8235             continue;
8236         }
8237         if (boardIndex >= forwardMostMove &&
8238             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8239             backwardMostMove = blackPlaysFirst ? 1 : 0;
8240             return;
8241         }
8242         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8243                                  fromY, fromX, toY, toX, promoChar,
8244                                  parseList[boardIndex]);
8245         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8246         /* currentMoveString is set as a side-effect of yylex */
8247         strcpy(moveList[boardIndex], currentMoveString);
8248         strcat(moveList[boardIndex], "\n");
8249         boardIndex++;
8250         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8251         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8252           case MT_NONE:
8253           case MT_STALEMATE:
8254           default:
8255             break;
8256           case MT_CHECK:
8257             if(gameInfo.variant != VariantShogi)
8258                 strcat(parseList[boardIndex - 1], "+");
8259             break;
8260           case MT_CHECKMATE:
8261           case MT_STAINMATE:
8262             strcat(parseList[boardIndex - 1], "#");
8263             break;
8264         }
8265     }
8266 }
8267
8268
8269 /* Apply a move to the given board  */
8270 void
8271 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8272      int fromX, fromY, toX, toY;
8273      int promoChar;
8274      Board board;
8275 {
8276   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8277   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8278
8279     /* [HGM] compute & store e.p. status and castling rights for new position */
8280     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8281
8282       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8283       oldEP = (signed char)board[EP_STATUS];
8284       board[EP_STATUS] = EP_NONE;
8285
8286       if( board[toY][toX] != EmptySquare ) 
8287            board[EP_STATUS] = EP_CAPTURE;  
8288
8289   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8290   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8291        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8292          
8293   if (fromY == DROP_RANK) {
8294         /* must be first */
8295         piece = board[toY][toX] = (ChessSquare) fromX;
8296   } else {
8297       int i;
8298
8299       if( board[fromY][fromX] == WhitePawn ) {
8300            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8301                board[EP_STATUS] = EP_PAWN_MOVE;
8302            if( toY-fromY==2) {
8303                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8304                         gameInfo.variant != VariantBerolina || toX < fromX)
8305                       board[EP_STATUS] = toX | berolina;
8306                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8307                         gameInfo.variant != VariantBerolina || toX > fromX) 
8308                       board[EP_STATUS] = toX;
8309            }
8310       } else 
8311       if( board[fromY][fromX] == BlackPawn ) {
8312            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8313                board[EP_STATUS] = EP_PAWN_MOVE; 
8314            if( toY-fromY== -2) {
8315                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8316                         gameInfo.variant != VariantBerolina || toX < fromX)
8317                       board[EP_STATUS] = toX | berolina;
8318                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8319                         gameInfo.variant != VariantBerolina || toX > fromX) 
8320                       board[EP_STATUS] = toX;
8321            }
8322        }
8323
8324        for(i=0; i<nrCastlingRights; i++) {
8325            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8326               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8327              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8328        }
8329
8330      if (fromX == toX && fromY == toY) return;
8331
8332      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8333      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8334      if(gameInfo.variant == VariantKnightmate)
8335          king += (int) WhiteUnicorn - (int) WhiteKing;
8336
8337     /* Code added by Tord: */
8338     /* FRC castling assumed when king captures friendly rook. */
8339     if (board[fromY][fromX] == WhiteKing &&
8340              board[toY][toX] == WhiteRook) {
8341       board[fromY][fromX] = EmptySquare;
8342       board[toY][toX] = EmptySquare;
8343       if(toX > fromX) {
8344         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8345       } else {
8346         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8347       }
8348     } else if (board[fromY][fromX] == BlackKing &&
8349                board[toY][toX] == BlackRook) {
8350       board[fromY][fromX] = EmptySquare;
8351       board[toY][toX] = EmptySquare;
8352       if(toX > fromX) {
8353         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8354       } else {
8355         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8356       }
8357     /* End of code added by Tord */
8358
8359     } else if (board[fromY][fromX] == king
8360         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8361         && toY == fromY && toX > fromX+1) {
8362         board[fromY][fromX] = EmptySquare;
8363         board[toY][toX] = king;
8364         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8365         board[fromY][BOARD_RGHT-1] = EmptySquare;
8366     } else if (board[fromY][fromX] == king
8367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8368                && toY == fromY && toX < fromX-1) {
8369         board[fromY][fromX] = EmptySquare;
8370         board[toY][toX] = king;
8371         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8372         board[fromY][BOARD_LEFT] = EmptySquare;
8373     } else if (board[fromY][fromX] == WhitePawn
8374                && toY >= BOARD_HEIGHT-promoRank
8375                && gameInfo.variant != VariantXiangqi
8376                ) {
8377         /* white pawn promotion */
8378         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8379         if (board[toY][toX] == EmptySquare) {
8380             board[toY][toX] = WhiteQueen;
8381         }
8382         if(gameInfo.variant==VariantBughouse ||
8383            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8384             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8385         board[fromY][fromX] = EmptySquare;
8386     } else if ((fromY == BOARD_HEIGHT-4)
8387                && (toX != fromX)
8388                && gameInfo.variant != VariantXiangqi
8389                && gameInfo.variant != VariantBerolina
8390                && (board[fromY][fromX] == WhitePawn)
8391                && (board[toY][toX] == EmptySquare)) {
8392         board[fromY][fromX] = EmptySquare;
8393         board[toY][toX] = WhitePawn;
8394         captured = board[toY - 1][toX];
8395         board[toY - 1][toX] = EmptySquare;
8396     } else if ((fromY == BOARD_HEIGHT-4)
8397                && (toX == fromX)
8398                && gameInfo.variant == VariantBerolina
8399                && (board[fromY][fromX] == WhitePawn)
8400                && (board[toY][toX] == EmptySquare)) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = WhitePawn;
8403         if(oldEP & EP_BEROLIN_A) {
8404                 captured = board[fromY][fromX-1];
8405                 board[fromY][fromX-1] = EmptySquare;
8406         }else{  captured = board[fromY][fromX+1];
8407                 board[fromY][fromX+1] = EmptySquare;
8408         }
8409     } else if (board[fromY][fromX] == king
8410         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8411                && toY == fromY && toX > fromX+1) {
8412         board[fromY][fromX] = EmptySquare;
8413         board[toY][toX] = king;
8414         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8415         board[fromY][BOARD_RGHT-1] = EmptySquare;
8416     } else if (board[fromY][fromX] == king
8417         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8418                && toY == fromY && toX < fromX-1) {
8419         board[fromY][fromX] = EmptySquare;
8420         board[toY][toX] = king;
8421         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8422         board[fromY][BOARD_LEFT] = EmptySquare;
8423     } else if (fromY == 7 && fromX == 3
8424                && board[fromY][fromX] == BlackKing
8425                && toY == 7 && toX == 5) {
8426         board[fromY][fromX] = EmptySquare;
8427         board[toY][toX] = BlackKing;
8428         board[fromY][7] = EmptySquare;
8429         board[toY][4] = BlackRook;
8430     } else if (fromY == 7 && fromX == 3
8431                && board[fromY][fromX] == BlackKing
8432                && toY == 7 && toX == 1) {
8433         board[fromY][fromX] = EmptySquare;
8434         board[toY][toX] = BlackKing;
8435         board[fromY][0] = EmptySquare;
8436         board[toY][2] = BlackRook;
8437     } else if (board[fromY][fromX] == BlackPawn
8438                && toY < promoRank
8439                && gameInfo.variant != VariantXiangqi
8440                ) {
8441         /* black pawn promotion */
8442         board[toY][toX] = CharToPiece(ToLower(promoChar));
8443         if (board[toY][toX] == EmptySquare) {
8444             board[toY][toX] = BlackQueen;
8445         }
8446         if(gameInfo.variant==VariantBughouse ||
8447            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8448             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8449         board[fromY][fromX] = EmptySquare;
8450     } else if ((fromY == 3)
8451                && (toX != fromX)
8452                && gameInfo.variant != VariantXiangqi
8453                && gameInfo.variant != VariantBerolina
8454                && (board[fromY][fromX] == BlackPawn)
8455                && (board[toY][toX] == EmptySquare)) {
8456         board[fromY][fromX] = EmptySquare;
8457         board[toY][toX] = BlackPawn;
8458         captured = board[toY + 1][toX];
8459         board[toY + 1][toX] = EmptySquare;
8460     } else if ((fromY == 3)
8461                && (toX == fromX)
8462                && gameInfo.variant == VariantBerolina
8463                && (board[fromY][fromX] == BlackPawn)
8464                && (board[toY][toX] == EmptySquare)) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = BlackPawn;
8467         if(oldEP & EP_BEROLIN_A) {
8468                 captured = board[fromY][fromX-1];
8469                 board[fromY][fromX-1] = EmptySquare;
8470         }else{  captured = board[fromY][fromX+1];
8471                 board[fromY][fromX+1] = EmptySquare;
8472         }
8473     } else {
8474         board[toY][toX] = board[fromY][fromX];
8475         board[fromY][fromX] = EmptySquare;
8476     }
8477
8478     /* [HGM] now we promote for Shogi, if needed */
8479     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8480         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8481   }
8482
8483     if (gameInfo.holdingsWidth != 0) {
8484
8485       /* !!A lot more code needs to be written to support holdings  */
8486       /* [HGM] OK, so I have written it. Holdings are stored in the */
8487       /* penultimate board files, so they are automaticlly stored   */
8488       /* in the game history.                                       */
8489       if (fromY == DROP_RANK) {
8490         /* Delete from holdings, by decreasing count */
8491         /* and erasing image if necessary            */
8492         p = (int) fromX;
8493         if(p < (int) BlackPawn) { /* white drop */
8494              p -= (int)WhitePawn;
8495                  p = PieceToNumber((ChessSquare)p);
8496              if(p >= gameInfo.holdingsSize) p = 0;
8497              if(--board[p][BOARD_WIDTH-2] <= 0)
8498                   board[p][BOARD_WIDTH-1] = EmptySquare;
8499              if((int)board[p][BOARD_WIDTH-2] < 0)
8500                         board[p][BOARD_WIDTH-2] = 0;
8501         } else {                  /* black drop */
8502              p -= (int)BlackPawn;
8503                  p = PieceToNumber((ChessSquare)p);
8504              if(p >= gameInfo.holdingsSize) p = 0;
8505              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8506                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8507              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8508                         board[BOARD_HEIGHT-1-p][1] = 0;
8509         }
8510       }
8511       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8512           && gameInfo.variant != VariantBughouse        ) {
8513         /* [HGM] holdings: Add to holdings, if holdings exist */
8514         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8515                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8516                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8517         }
8518         p = (int) captured;
8519         if (p >= (int) BlackPawn) {
8520           p -= (int)BlackPawn;
8521           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8522                   /* in Shogi restore piece to its original  first */
8523                   captured = (ChessSquare) (DEMOTED captured);
8524                   p = DEMOTED p;
8525           }
8526           p = PieceToNumber((ChessSquare)p);
8527           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8528           board[p][BOARD_WIDTH-2]++;
8529           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8530         } else {
8531           p -= (int)WhitePawn;
8532           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8533                   captured = (ChessSquare) (DEMOTED captured);
8534                   p = DEMOTED p;
8535           }
8536           p = PieceToNumber((ChessSquare)p);
8537           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8538           board[BOARD_HEIGHT-1-p][1]++;
8539           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8540         }
8541       }
8542     } else if (gameInfo.variant == VariantAtomic) {
8543       if (captured != EmptySquare) {
8544         int y, x;
8545         for (y = toY-1; y <= toY+1; y++) {
8546           for (x = toX-1; x <= toX+1; x++) {
8547             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8548                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8549               board[y][x] = EmptySquare;
8550             }
8551           }
8552         }
8553         board[toY][toX] = EmptySquare;
8554       }
8555     }
8556     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8557         /* [HGM] Shogi promotions */
8558         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8559     }
8560
8561     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8562                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8563         // [HGM] superchess: take promotion piece out of holdings
8564         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8565         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8566             if(!--board[k][BOARD_WIDTH-2])
8567                 board[k][BOARD_WIDTH-1] = EmptySquare;
8568         } else {
8569             if(!--board[BOARD_HEIGHT-1-k][1])
8570                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8571         }
8572     }
8573
8574 }
8575
8576 /* Updates forwardMostMove */
8577 void
8578 MakeMove(fromX, fromY, toX, toY, promoChar)
8579      int fromX, fromY, toX, toY;
8580      int promoChar;
8581 {
8582 //    forwardMostMove++; // [HGM] bare: moved downstream
8583
8584     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8585         int timeLeft; static int lastLoadFlag=0; int king, piece;
8586         piece = boards[forwardMostMove][fromY][fromX];
8587         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8588         if(gameInfo.variant == VariantKnightmate)
8589             king += (int) WhiteUnicorn - (int) WhiteKing;
8590         if(forwardMostMove == 0) {
8591             if(blackPlaysFirst) 
8592                 fprintf(serverMoves, "%s;", second.tidy);
8593             fprintf(serverMoves, "%s;", first.tidy);
8594             if(!blackPlaysFirst) 
8595                 fprintf(serverMoves, "%s;", second.tidy);
8596         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8597         lastLoadFlag = loadFlag;
8598         // print base move
8599         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8600         // print castling suffix
8601         if( toY == fromY && piece == king ) {
8602             if(toX-fromX > 1)
8603                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8604             if(fromX-toX >1)
8605                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8606         }
8607         // e.p. suffix
8608         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8609              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8610              boards[forwardMostMove][toY][toX] == EmptySquare
8611              && fromX != toX && fromY != toY)
8612                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8613         // promotion suffix
8614         if(promoChar != NULLCHAR)
8615                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8616         if(!loadFlag) {
8617             fprintf(serverMoves, "/%d/%d",
8618                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8619             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8620             else                      timeLeft = blackTimeRemaining/1000;
8621             fprintf(serverMoves, "/%d", timeLeft);
8622         }
8623         fflush(serverMoves);
8624     }
8625
8626     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8627       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8628                         0, 1);
8629       return;
8630     }
8631     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8632     if (commentList[forwardMostMove+1] != NULL) {
8633         free(commentList[forwardMostMove+1]);
8634         commentList[forwardMostMove+1] = NULL;
8635     }
8636     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8637     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8638     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8639     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8640     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8641     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8642     gameInfo.result = GameUnfinished;
8643     if (gameInfo.resultDetails != NULL) {
8644         free(gameInfo.resultDetails);
8645         gameInfo.resultDetails = NULL;
8646     }
8647     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8648                               moveList[forwardMostMove - 1]);
8649     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8650                              PosFlags(forwardMostMove - 1),
8651                              fromY, fromX, toY, toX, promoChar,
8652                              parseList[forwardMostMove - 1]);
8653     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8654       case MT_NONE:
8655       case MT_STALEMATE:
8656       default:
8657         break;
8658       case MT_CHECK:
8659         if(gameInfo.variant != VariantShogi)
8660             strcat(parseList[forwardMostMove - 1], "+");
8661         break;
8662       case MT_CHECKMATE:
8663       case MT_STAINMATE:
8664         strcat(parseList[forwardMostMove - 1], "#");
8665         break;
8666     }
8667     if (appData.debugMode) {
8668         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8669     }
8670
8671 }
8672
8673 /* Updates currentMove if not pausing */
8674 void
8675 ShowMove(fromX, fromY, toX, toY)
8676 {
8677     int instant = (gameMode == PlayFromGameFile) ?
8678         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8679     if(appData.noGUI) return;
8680     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8681         if (!instant) {
8682             if (forwardMostMove == currentMove + 1) {
8683                 AnimateMove(boards[forwardMostMove - 1],
8684                             fromX, fromY, toX, toY);
8685             }
8686             if (appData.highlightLastMove) {
8687                 SetHighlights(fromX, fromY, toX, toY);
8688             }
8689         }
8690         currentMove = forwardMostMove;
8691     }
8692
8693     if (instant) return;
8694
8695     DisplayMove(currentMove - 1);
8696     DrawPosition(FALSE, boards[currentMove]);
8697     DisplayBothClocks();
8698     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8699 }
8700
8701 void SendEgtPath(ChessProgramState *cps)
8702 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8703         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8704
8705         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8706
8707         while(*p) {
8708             char c, *q = name+1, *r, *s;
8709
8710             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8711             while(*p && *p != ',') *q++ = *p++;
8712             *q++ = ':'; *q = 0;
8713             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8714                 strcmp(name, ",nalimov:") == 0 ) {
8715                 // take nalimov path from the menu-changeable option first, if it is defined
8716                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8717                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8718             } else
8719             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8720                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8721                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8722                 s = r = StrStr(s, ":") + 1; // beginning of path info
8723                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8724                 c = *r; *r = 0;             // temporarily null-terminate path info
8725                     *--q = 0;               // strip of trailig ':' from name
8726                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8727                 *r = c;
8728                 SendToProgram(buf,cps);     // send egtbpath command for this format
8729             }
8730             if(*p == ',') p++; // read away comma to position for next format name
8731         }
8732 }
8733
8734 void
8735 InitChessProgram(cps, setup)
8736      ChessProgramState *cps;
8737      int setup; /* [HGM] needed to setup FRC opening position */
8738 {
8739     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8740     if (appData.noChessProgram) return;
8741     hintRequested = FALSE;
8742     bookRequested = FALSE;
8743
8744     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8745     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8746     if(cps->memSize) { /* [HGM] memory */
8747         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8748         SendToProgram(buf, cps);
8749     }
8750     SendEgtPath(cps); /* [HGM] EGT */
8751     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8752         sprintf(buf, "cores %d\n", appData.smpCores);
8753         SendToProgram(buf, cps);
8754     }
8755
8756     SendToProgram(cps->initString, cps);
8757     if (gameInfo.variant != VariantNormal &&
8758         gameInfo.variant != VariantLoadable
8759         /* [HGM] also send variant if board size non-standard */
8760         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8761                                             ) {
8762       char *v = VariantName(gameInfo.variant);
8763       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8764         /* [HGM] in protocol 1 we have to assume all variants valid */
8765         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8766         DisplayFatalError(buf, 0, 1);
8767         return;
8768       }
8769
8770       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8771       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8772       if( gameInfo.variant == VariantXiangqi )
8773            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8774       if( gameInfo.variant == VariantShogi )
8775            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8776       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8777            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8778       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8779                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8780            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8781       if( gameInfo.variant == VariantCourier )
8782            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8783       if( gameInfo.variant == VariantSuper )
8784            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8785       if( gameInfo.variant == VariantGreat )
8786            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8787
8788       if(overruled) {
8789            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8790                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8791            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8792            if(StrStr(cps->variants, b) == NULL) { 
8793                // specific sized variant not known, check if general sizing allowed
8794                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8795                    if(StrStr(cps->variants, "boardsize") == NULL) {
8796                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8797                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8798                        DisplayFatalError(buf, 0, 1);
8799                        return;
8800                    }
8801                    /* [HGM] here we really should compare with the maximum supported board size */
8802                }
8803            }
8804       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8805       sprintf(buf, "variant %s\n", b);
8806       SendToProgram(buf, cps);
8807     }
8808     currentlyInitializedVariant = gameInfo.variant;
8809
8810     /* [HGM] send opening position in FRC to first engine */
8811     if(setup) {
8812           SendToProgram("force\n", cps);
8813           SendBoard(cps, 0);
8814           /* engine is now in force mode! Set flag to wake it up after first move. */
8815           setboardSpoiledMachineBlack = 1;
8816     }
8817
8818     if (cps->sendICS) {
8819       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8820       SendToProgram(buf, cps);
8821     }
8822     cps->maybeThinking = FALSE;
8823     cps->offeredDraw = 0;
8824     if (!appData.icsActive) {
8825         SendTimeControl(cps, movesPerSession, timeControl,
8826                         timeIncrement, appData.searchDepth,
8827                         searchTime);
8828     }
8829     if (appData.showThinking 
8830         // [HGM] thinking: four options require thinking output to be sent
8831         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8832                                 ) {
8833         SendToProgram("post\n", cps);
8834     }
8835     SendToProgram("hard\n", cps);
8836     if (!appData.ponderNextMove) {
8837         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8838            it without being sure what state we are in first.  "hard"
8839            is not a toggle, so that one is OK.
8840          */
8841         SendToProgram("easy\n", cps);
8842     }
8843     if (cps->usePing) {
8844       sprintf(buf, "ping %d\n", ++cps->lastPing);
8845       SendToProgram(buf, cps);
8846     }
8847     cps->initDone = TRUE;
8848 }   
8849
8850
8851 void
8852 StartChessProgram(cps)
8853      ChessProgramState *cps;
8854 {
8855     char buf[MSG_SIZ];
8856     int err;
8857
8858     if (appData.noChessProgram) return;
8859     cps->initDone = FALSE;
8860
8861     if (strcmp(cps->host, "localhost") == 0) {
8862         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8863     } else if (*appData.remoteShell == NULLCHAR) {
8864         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8865     } else {
8866         if (*appData.remoteUser == NULLCHAR) {
8867           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8868                     cps->program);
8869         } else {
8870           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8871                     cps->host, appData.remoteUser, cps->program);
8872         }
8873         err = StartChildProcess(buf, "", &cps->pr);
8874     }
8875     
8876     if (err != 0) {
8877         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8878         DisplayFatalError(buf, err, 1);
8879         cps->pr = NoProc;
8880         cps->isr = NULL;
8881         return;
8882     }
8883     
8884     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8885     if (cps->protocolVersion > 1) {
8886       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8887       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8888       cps->comboCnt = 0;  //                and values of combo boxes
8889       SendToProgram(buf, cps);
8890     } else {
8891       SendToProgram("xboard\n", cps);
8892     }
8893 }
8894
8895
8896 void
8897 TwoMachinesEventIfReady P((void))
8898 {
8899   if (first.lastPing != first.lastPong) {
8900     DisplayMessage("", _("Waiting for first chess program"));
8901     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8902     return;
8903   }
8904   if (second.lastPing != second.lastPong) {
8905     DisplayMessage("", _("Waiting for second chess program"));
8906     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8907     return;
8908   }
8909   ThawUI();
8910   TwoMachinesEvent();
8911 }
8912
8913 void
8914 NextMatchGame P((void))
8915 {
8916     int index; /* [HGM] autoinc: step load index during match */
8917     Reset(FALSE, TRUE);
8918     if (*appData.loadGameFile != NULLCHAR) {
8919         index = appData.loadGameIndex;
8920         if(index < 0) { // [HGM] autoinc
8921             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8922             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8923         } 
8924         LoadGameFromFile(appData.loadGameFile,
8925                          index,
8926                          appData.loadGameFile, FALSE);
8927     } else if (*appData.loadPositionFile != NULLCHAR) {
8928         index = appData.loadPositionIndex;
8929         if(index < 0) { // [HGM] autoinc
8930             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8931             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8932         } 
8933         LoadPositionFromFile(appData.loadPositionFile,
8934                              index,
8935                              appData.loadPositionFile);
8936     }
8937     TwoMachinesEventIfReady();
8938 }
8939
8940 void UserAdjudicationEvent( int result )
8941 {
8942     ChessMove gameResult = GameIsDrawn;
8943
8944     if( result > 0 ) {
8945         gameResult = WhiteWins;
8946     }
8947     else if( result < 0 ) {
8948         gameResult = BlackWins;
8949     }
8950
8951     if( gameMode == TwoMachinesPlay ) {
8952         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8953     }
8954 }
8955
8956
8957 // [HGM] save: calculate checksum of game to make games easily identifiable
8958 int StringCheckSum(char *s)
8959 {
8960         int i = 0;
8961         if(s==NULL) return 0;
8962         while(*s) i = i*259 + *s++;
8963         return i;
8964 }
8965
8966 int GameCheckSum()
8967 {
8968         int i, sum=0;
8969         for(i=backwardMostMove; i<forwardMostMove; i++) {
8970                 sum += pvInfoList[i].depth;
8971                 sum += StringCheckSum(parseList[i]);
8972                 sum += StringCheckSum(commentList[i]);
8973                 sum *= 261;
8974         }
8975         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8976         return sum + StringCheckSum(commentList[i]);
8977 } // end of save patch
8978
8979 void
8980 GameEnds(result, resultDetails, whosays)
8981      ChessMove result;
8982      char *resultDetails;
8983      int whosays;
8984 {
8985     GameMode nextGameMode;
8986     int isIcsGame;
8987     char buf[MSG_SIZ], popupRequested = 0;
8988
8989     if(endingGame) return; /* [HGM] crash: forbid recursion */
8990     endingGame = 1;
8991     if(twoBoards) { // [HGM] dual: switch back to one board
8992         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8993         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8994     }
8995     if (appData.debugMode) {
8996       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8997               result, resultDetails ? resultDetails : "(null)", whosays);
8998     }
8999
9000     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9001
9002     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9003         /* If we are playing on ICS, the server decides when the
9004            game is over, but the engine can offer to draw, claim 
9005            a draw, or resign. 
9006          */
9007 #if ZIPPY
9008         if (appData.zippyPlay && first.initDone) {
9009             if (result == GameIsDrawn) {
9010                 /* In case draw still needs to be claimed */
9011                 SendToICS(ics_prefix);
9012                 SendToICS("draw\n");
9013             } else if (StrCaseStr(resultDetails, "resign")) {
9014                 SendToICS(ics_prefix);
9015                 SendToICS("resign\n");
9016             }
9017         }
9018 #endif
9019         endingGame = 0; /* [HGM] crash */
9020         return;
9021     }
9022
9023     /* If we're loading the game from a file, stop */
9024     if (whosays == GE_FILE) {
9025       (void) StopLoadGameTimer();
9026       gameFileFP = NULL;
9027     }
9028
9029     /* Cancel draw offers */
9030     first.offeredDraw = second.offeredDraw = 0;
9031
9032     /* If this is an ICS game, only ICS can really say it's done;
9033        if not, anyone can. */
9034     isIcsGame = (gameMode == IcsPlayingWhite || 
9035                  gameMode == IcsPlayingBlack || 
9036                  gameMode == IcsObserving    || 
9037                  gameMode == IcsExamining);
9038
9039     if (!isIcsGame || whosays == GE_ICS) {
9040         /* OK -- not an ICS game, or ICS said it was done */
9041         StopClocks();
9042         if (!isIcsGame && !appData.noChessProgram) 
9043           SetUserThinkingEnables();
9044     
9045         /* [HGM] if a machine claims the game end we verify this claim */
9046         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9047             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9048                 char claimer;
9049                 ChessMove trueResult = (ChessMove) -1;
9050
9051                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9052                                             first.twoMachinesColor[0] :
9053                                             second.twoMachinesColor[0] ;
9054
9055                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9056                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9057                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9058                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9059                 } else
9060                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9061                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9062                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9063                 } else
9064                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9065                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9066                 }
9067
9068                 // now verify win claims, but not in drop games, as we don't understand those yet
9069                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9070                                                  || gameInfo.variant == VariantGreat) &&
9071                     (result == WhiteWins && claimer == 'w' ||
9072                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9073                       if (appData.debugMode) {
9074                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9075                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9076                       }
9077                       if(result != trueResult) {
9078                               sprintf(buf, "False win claim: '%s'", resultDetails);
9079                               result = claimer == 'w' ? BlackWins : WhiteWins;
9080                               resultDetails = buf;
9081                       }
9082                 } else
9083                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9084                     && (forwardMostMove <= backwardMostMove ||
9085                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9086                         (claimer=='b')==(forwardMostMove&1))
9087                                                                                   ) {
9088                       /* [HGM] verify: draws that were not flagged are false claims */
9089                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9090                       result = claimer == 'w' ? BlackWins : WhiteWins;
9091                       resultDetails = buf;
9092                 }
9093                 /* (Claiming a loss is accepted no questions asked!) */
9094             }
9095             /* [HGM] bare: don't allow bare King to win */
9096             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9097                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9098                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9099                && result != GameIsDrawn)
9100             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9101                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9102                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9103                         if(p >= 0 && p <= (int)WhiteKing) k++;
9104                 }
9105                 if (appData.debugMode) {
9106                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9107                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9108                 }
9109                 if(k <= 1) {
9110                         result = GameIsDrawn;
9111                         sprintf(buf, "%s but bare king", resultDetails);
9112                         resultDetails = buf;
9113                 }
9114             }
9115         }
9116
9117
9118         if(serverMoves != NULL && !loadFlag) { char c = '=';
9119             if(result==WhiteWins) c = '+';
9120             if(result==BlackWins) c = '-';
9121             if(resultDetails != NULL)
9122                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9123         }
9124         if (resultDetails != NULL) {
9125             gameInfo.result = result;
9126             gameInfo.resultDetails = StrSave(resultDetails);
9127
9128             /* display last move only if game was not loaded from file */
9129             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9130                 DisplayMove(currentMove - 1);
9131     
9132             if (forwardMostMove != 0) {
9133                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9134                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9135                                                                 ) {
9136                     if (*appData.saveGameFile != NULLCHAR) {
9137                         SaveGameToFile(appData.saveGameFile, TRUE);
9138                     } else if (appData.autoSaveGames) {
9139                         AutoSaveGame();
9140                     }
9141                     if (*appData.savePositionFile != NULLCHAR) {
9142                         SavePositionToFile(appData.savePositionFile);
9143                     }
9144                 }
9145             }
9146
9147             /* Tell program how game ended in case it is learning */
9148             /* [HGM] Moved this to after saving the PGN, just in case */
9149             /* engine died and we got here through time loss. In that */
9150             /* case we will get a fatal error writing the pipe, which */
9151             /* would otherwise lose us the PGN.                       */
9152             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9153             /* output during GameEnds should never be fatal anymore   */
9154             if (gameMode == MachinePlaysWhite ||
9155                 gameMode == MachinePlaysBlack ||
9156                 gameMode == TwoMachinesPlay ||
9157                 gameMode == IcsPlayingWhite ||
9158                 gameMode == IcsPlayingBlack ||
9159                 gameMode == BeginningOfGame) {
9160                 char buf[MSG_SIZ];
9161                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9162                         resultDetails);
9163                 if (first.pr != NoProc) {
9164                     SendToProgram(buf, &first);
9165                 }
9166                 if (second.pr != NoProc &&
9167                     gameMode == TwoMachinesPlay) {
9168                     SendToProgram(buf, &second);
9169                 }
9170             }
9171         }
9172
9173         if (appData.icsActive) {
9174             if (appData.quietPlay &&
9175                 (gameMode == IcsPlayingWhite ||
9176                  gameMode == IcsPlayingBlack)) {
9177                 SendToICS(ics_prefix);
9178                 SendToICS("set shout 1\n");
9179             }
9180             nextGameMode = IcsIdle;
9181             ics_user_moved = FALSE;
9182             /* clean up premove.  It's ugly when the game has ended and the
9183              * premove highlights are still on the board.
9184              */
9185             if (gotPremove) {
9186               gotPremove = FALSE;
9187               ClearPremoveHighlights();
9188               DrawPosition(FALSE, boards[currentMove]);
9189             }
9190             if (whosays == GE_ICS) {
9191                 switch (result) {
9192                 case WhiteWins:
9193                     if (gameMode == IcsPlayingWhite)
9194                         PlayIcsWinSound();
9195                     else if(gameMode == IcsPlayingBlack)
9196                         PlayIcsLossSound();
9197                     break;
9198                 case BlackWins:
9199                     if (gameMode == IcsPlayingBlack)
9200                         PlayIcsWinSound();
9201                     else if(gameMode == IcsPlayingWhite)
9202                         PlayIcsLossSound();
9203                     break;
9204                 case GameIsDrawn:
9205                     PlayIcsDrawSound();
9206                     break;
9207                 default:
9208                     PlayIcsUnfinishedSound();
9209                 }
9210             }
9211         } else if (gameMode == EditGame ||
9212                    gameMode == PlayFromGameFile || 
9213                    gameMode == AnalyzeMode || 
9214                    gameMode == AnalyzeFile) {
9215             nextGameMode = gameMode;
9216         } else {
9217             nextGameMode = EndOfGame;
9218         }
9219         pausing = FALSE;
9220         ModeHighlight();
9221     } else {
9222         nextGameMode = gameMode;
9223     }
9224
9225     if (appData.noChessProgram) {
9226         gameMode = nextGameMode;
9227         ModeHighlight();
9228         endingGame = 0; /* [HGM] crash */
9229         return;
9230     }
9231
9232     if (first.reuse) {
9233         /* Put first chess program into idle state */
9234         if (first.pr != NoProc &&
9235             (gameMode == MachinePlaysWhite ||
9236              gameMode == MachinePlaysBlack ||
9237              gameMode == TwoMachinesPlay ||
9238              gameMode == IcsPlayingWhite ||
9239              gameMode == IcsPlayingBlack ||
9240              gameMode == BeginningOfGame)) {
9241             SendToProgram("force\n", &first);
9242             if (first.usePing) {
9243               char buf[MSG_SIZ];
9244               sprintf(buf, "ping %d\n", ++first.lastPing);
9245               SendToProgram(buf, &first);
9246             }
9247         }
9248     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9249         /* Kill off first chess program */
9250         if (first.isr != NULL)
9251           RemoveInputSource(first.isr);
9252         first.isr = NULL;
9253     
9254         if (first.pr != NoProc) {
9255             ExitAnalyzeMode();
9256             DoSleep( appData.delayBeforeQuit );
9257             SendToProgram("quit\n", &first);
9258             DoSleep( appData.delayAfterQuit );
9259             DestroyChildProcess(first.pr, first.useSigterm);
9260         }
9261         first.pr = NoProc;
9262     }
9263     if (second.reuse) {
9264         /* Put second chess program into idle state */
9265         if (second.pr != NoProc &&
9266             gameMode == TwoMachinesPlay) {
9267             SendToProgram("force\n", &second);
9268             if (second.usePing) {
9269               char buf[MSG_SIZ];
9270               sprintf(buf, "ping %d\n", ++second.lastPing);
9271               SendToProgram(buf, &second);
9272             }
9273         }
9274     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9275         /* Kill off second chess program */
9276         if (second.isr != NULL)
9277           RemoveInputSource(second.isr);
9278         second.isr = NULL;
9279     
9280         if (second.pr != NoProc) {
9281             DoSleep( appData.delayBeforeQuit );
9282             SendToProgram("quit\n", &second);
9283             DoSleep( appData.delayAfterQuit );
9284             DestroyChildProcess(second.pr, second.useSigterm);
9285         }
9286         second.pr = NoProc;
9287     }
9288
9289     if (matchMode && gameMode == TwoMachinesPlay) {
9290         switch (result) {
9291         case WhiteWins:
9292           if (first.twoMachinesColor[0] == 'w') {
9293             first.matchWins++;
9294           } else {
9295             second.matchWins++;
9296           }
9297           break;
9298         case BlackWins:
9299           if (first.twoMachinesColor[0] == 'b') {
9300             first.matchWins++;
9301           } else {
9302             second.matchWins++;
9303           }
9304           break;
9305         default:
9306           break;
9307         }
9308         if (matchGame < appData.matchGames) {
9309             char *tmp;
9310             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9311                 tmp = first.twoMachinesColor;
9312                 first.twoMachinesColor = second.twoMachinesColor;
9313                 second.twoMachinesColor = tmp;
9314             }
9315             gameMode = nextGameMode;
9316             matchGame++;
9317             if(appData.matchPause>10000 || appData.matchPause<10)
9318                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9319             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9320             endingGame = 0; /* [HGM] crash */
9321             return;
9322         } else {
9323             gameMode = nextGameMode;
9324             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9325                     first.tidy, second.tidy,
9326                     first.matchWins, second.matchWins,
9327                     appData.matchGames - (first.matchWins + second.matchWins));
9328             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9329         }
9330     }
9331     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9332         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9333       ExitAnalyzeMode();
9334     gameMode = nextGameMode;
9335     ModeHighlight();
9336     endingGame = 0;  /* [HGM] crash */
9337     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9338       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9339         matchMode = FALSE; appData.matchGames = matchGame = 0;
9340         DisplayNote(buf);
9341       }
9342     }
9343 }
9344
9345 /* Assumes program was just initialized (initString sent).
9346    Leaves program in force mode. */
9347 void
9348 FeedMovesToProgram(cps, upto) 
9349      ChessProgramState *cps;
9350      int upto;
9351 {
9352     int i;
9353     
9354     if (appData.debugMode)
9355       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9356               startedFromSetupPosition ? "position and " : "",
9357               backwardMostMove, upto, cps->which);
9358     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9359         // [HGM] variantswitch: make engine aware of new variant
9360         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9361                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9362         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9363         SendToProgram(buf, cps);
9364         currentlyInitializedVariant = gameInfo.variant;
9365     }
9366     SendToProgram("force\n", cps);
9367     if (startedFromSetupPosition) {
9368         SendBoard(cps, backwardMostMove);
9369     if (appData.debugMode) {
9370         fprintf(debugFP, "feedMoves\n");
9371     }
9372     }
9373     for (i = backwardMostMove; i < upto; i++) {
9374         SendMoveToProgram(i, cps);
9375     }
9376 }
9377
9378
9379 void
9380 ResurrectChessProgram()
9381 {
9382      /* The chess program may have exited.
9383         If so, restart it and feed it all the moves made so far. */
9384
9385     if (appData.noChessProgram || first.pr != NoProc) return;
9386     
9387     StartChessProgram(&first);
9388     InitChessProgram(&first, FALSE);
9389     FeedMovesToProgram(&first, currentMove);
9390
9391     if (!first.sendTime) {
9392         /* can't tell gnuchess what its clock should read,
9393            so we bow to its notion. */
9394         ResetClocks();
9395         timeRemaining[0][currentMove] = whiteTimeRemaining;
9396         timeRemaining[1][currentMove] = blackTimeRemaining;
9397     }
9398
9399     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9400                 appData.icsEngineAnalyze) && first.analysisSupport) {
9401       SendToProgram("analyze\n", &first);
9402       first.analyzing = TRUE;
9403     }
9404 }
9405
9406 /*
9407  * Button procedures
9408  */
9409 void
9410 Reset(redraw, init)
9411      int redraw, init;
9412 {
9413     int i;
9414
9415     if (appData.debugMode) {
9416         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9417                 redraw, init, gameMode);
9418     }
9419     CleanupTail(); // [HGM] vari: delete any stored variations
9420     pausing = pauseExamInvalid = FALSE;
9421     startedFromSetupPosition = blackPlaysFirst = FALSE;
9422     firstMove = TRUE;
9423     whiteFlag = blackFlag = FALSE;
9424     userOfferedDraw = FALSE;
9425     hintRequested = bookRequested = FALSE;
9426     first.maybeThinking = FALSE;
9427     second.maybeThinking = FALSE;
9428     first.bookSuspend = FALSE; // [HGM] book
9429     second.bookSuspend = FALSE;
9430     thinkOutput[0] = NULLCHAR;
9431     lastHint[0] = NULLCHAR;
9432     ClearGameInfo(&gameInfo);
9433     gameInfo.variant = StringToVariant(appData.variant);
9434     ics_user_moved = ics_clock_paused = FALSE;
9435     ics_getting_history = H_FALSE;
9436     ics_gamenum = -1;
9437     white_holding[0] = black_holding[0] = NULLCHAR;
9438     ClearProgramStats();
9439     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9440     
9441     ResetFrontEnd();
9442     ClearHighlights();
9443     flipView = appData.flipView;
9444     ClearPremoveHighlights();
9445     gotPremove = FALSE;
9446     alarmSounded = FALSE;
9447
9448     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9449     if(appData.serverMovesName != NULL) {
9450         /* [HGM] prepare to make moves file for broadcasting */
9451         clock_t t = clock();
9452         if(serverMoves != NULL) fclose(serverMoves);
9453         serverMoves = fopen(appData.serverMovesName, "r");
9454         if(serverMoves != NULL) {
9455             fclose(serverMoves);
9456             /* delay 15 sec before overwriting, so all clients can see end */
9457             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9458         }
9459         serverMoves = fopen(appData.serverMovesName, "w");
9460     }
9461
9462     ExitAnalyzeMode();
9463     gameMode = BeginningOfGame;
9464     ModeHighlight();
9465     if(appData.icsActive) gameInfo.variant = VariantNormal;
9466     currentMove = forwardMostMove = backwardMostMove = 0;
9467     InitPosition(redraw);
9468     for (i = 0; i < MAX_MOVES; i++) {
9469         if (commentList[i] != NULL) {
9470             free(commentList[i]);
9471             commentList[i] = NULL;
9472         }
9473     }
9474     ResetClocks();
9475     timeRemaining[0][0] = whiteTimeRemaining;
9476     timeRemaining[1][0] = blackTimeRemaining;
9477     if (first.pr == NULL) {
9478         StartChessProgram(&first);
9479     }
9480     if (init) {
9481             InitChessProgram(&first, startedFromSetupPosition);
9482     }
9483     DisplayTitle("");
9484     DisplayMessage("", "");
9485     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9486     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9487 }
9488
9489 void
9490 AutoPlayGameLoop()
9491 {
9492     for (;;) {
9493         if (!AutoPlayOneMove())
9494           return;
9495         if (matchMode || appData.timeDelay == 0)
9496           continue;
9497         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9498           return;
9499         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9500         break;
9501     }
9502 }
9503
9504
9505 int
9506 AutoPlayOneMove()
9507 {
9508     int fromX, fromY, toX, toY;
9509
9510     if (appData.debugMode) {
9511       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9512     }
9513
9514     if (gameMode != PlayFromGameFile)
9515       return FALSE;
9516
9517     if (currentMove >= forwardMostMove) {
9518       gameMode = EditGame;
9519       ModeHighlight();
9520
9521       /* [AS] Clear current move marker at the end of a game */
9522       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9523
9524       return FALSE;
9525     }
9526     
9527     toX = moveList[currentMove][2] - AAA;
9528     toY = moveList[currentMove][3] - ONE;
9529
9530     if (moveList[currentMove][1] == '@') {
9531         if (appData.highlightLastMove) {
9532             SetHighlights(-1, -1, toX, toY);
9533         }
9534     } else {
9535         fromX = moveList[currentMove][0] - AAA;
9536         fromY = moveList[currentMove][1] - ONE;
9537
9538         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9539
9540         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9541
9542         if (appData.highlightLastMove) {
9543             SetHighlights(fromX, fromY, toX, toY);
9544         }
9545     }
9546     DisplayMove(currentMove);
9547     SendMoveToProgram(currentMove++, &first);
9548     DisplayBothClocks();
9549     DrawPosition(FALSE, boards[currentMove]);
9550     // [HGM] PV info: always display, routine tests if empty
9551     DisplayComment(currentMove - 1, commentList[currentMove]);
9552     return TRUE;
9553 }
9554
9555
9556 int
9557 LoadGameOneMove(readAhead)
9558      ChessMove readAhead;
9559 {
9560     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9561     char promoChar = NULLCHAR;
9562     ChessMove moveType;
9563     char move[MSG_SIZ];
9564     char *p, *q;
9565     
9566     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9567         gameMode != AnalyzeMode && gameMode != Training) {
9568         gameFileFP = NULL;
9569         return FALSE;
9570     }
9571     
9572     yyboardindex = forwardMostMove;
9573     if (readAhead != (ChessMove)0) {
9574       moveType = readAhead;
9575     } else {
9576       if (gameFileFP == NULL)
9577           return FALSE;
9578       moveType = (ChessMove) yylex();
9579     }
9580     
9581     done = FALSE;
9582     switch (moveType) {
9583       case Comment:
9584         if (appData.debugMode) 
9585           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9586         p = yy_text;
9587
9588         /* append the comment but don't display it */
9589         AppendComment(currentMove, p, FALSE);
9590         return TRUE;
9591
9592       case WhiteCapturesEnPassant:
9593       case BlackCapturesEnPassant:
9594       case WhitePromotionChancellor:
9595       case BlackPromotionChancellor:
9596       case WhitePromotionArchbishop:
9597       case BlackPromotionArchbishop:
9598       case WhitePromotionCentaur:
9599       case BlackPromotionCentaur:
9600       case WhitePromotionQueen:
9601       case BlackPromotionQueen:
9602       case WhitePromotionRook:
9603       case BlackPromotionRook:
9604       case WhitePromotionBishop:
9605       case BlackPromotionBishop:
9606       case WhitePromotionKnight:
9607       case BlackPromotionKnight:
9608       case WhitePromotionKing:
9609       case BlackPromotionKing:
9610       case WhiteNonPromotion:
9611       case BlackNonPromotion:
9612       case NormalMove:
9613       case WhiteKingSideCastle:
9614       case WhiteQueenSideCastle:
9615       case BlackKingSideCastle:
9616       case BlackQueenSideCastle:
9617       case WhiteKingSideCastleWild:
9618       case WhiteQueenSideCastleWild:
9619       case BlackKingSideCastleWild:
9620       case BlackQueenSideCastleWild:
9621       /* PUSH Fabien */
9622       case WhiteHSideCastleFR:
9623       case WhiteASideCastleFR:
9624       case BlackHSideCastleFR:
9625       case BlackASideCastleFR:
9626       /* POP Fabien */
9627         if (appData.debugMode)
9628           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9629         fromX = currentMoveString[0] - AAA;
9630         fromY = currentMoveString[1] - ONE;
9631         toX = currentMoveString[2] - AAA;
9632         toY = currentMoveString[3] - ONE;
9633         promoChar = currentMoveString[4];
9634         break;
9635
9636       case WhiteDrop:
9637       case BlackDrop:
9638         if (appData.debugMode)
9639           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9640         fromX = moveType == WhiteDrop ?
9641           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9642         (int) CharToPiece(ToLower(currentMoveString[0]));
9643         fromY = DROP_RANK;
9644         toX = currentMoveString[2] - AAA;
9645         toY = currentMoveString[3] - ONE;
9646         break;
9647
9648       case WhiteWins:
9649       case BlackWins:
9650       case GameIsDrawn:
9651       case GameUnfinished:
9652         if (appData.debugMode)
9653           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9654         p = strchr(yy_text, '{');
9655         if (p == NULL) p = strchr(yy_text, '(');
9656         if (p == NULL) {
9657             p = yy_text;
9658             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9659         } else {
9660             q = strchr(p, *p == '{' ? '}' : ')');
9661             if (q != NULL) *q = NULLCHAR;
9662             p++;
9663         }
9664         GameEnds(moveType, p, GE_FILE);
9665         done = TRUE;
9666         if (cmailMsgLoaded) {
9667             ClearHighlights();
9668             flipView = WhiteOnMove(currentMove);
9669             if (moveType == GameUnfinished) flipView = !flipView;
9670             if (appData.debugMode)
9671               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9672         }
9673         break;
9674
9675       case (ChessMove) 0:       /* end of file */
9676         if (appData.debugMode)
9677           fprintf(debugFP, "Parser hit end of file\n");
9678         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9679           case MT_NONE:
9680           case MT_CHECK:
9681             break;
9682           case MT_CHECKMATE:
9683           case MT_STAINMATE:
9684             if (WhiteOnMove(currentMove)) {
9685                 GameEnds(BlackWins, "Black mates", GE_FILE);
9686             } else {
9687                 GameEnds(WhiteWins, "White mates", GE_FILE);
9688             }
9689             break;
9690           case MT_STALEMATE:
9691             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9692             break;
9693         }
9694         done = TRUE;
9695         break;
9696
9697       case MoveNumberOne:
9698         if (lastLoadGameStart == GNUChessGame) {
9699             /* GNUChessGames have numbers, but they aren't move numbers */
9700             if (appData.debugMode)
9701               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9702                       yy_text, (int) moveType);
9703             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9704         }
9705         /* else fall thru */
9706
9707       case XBoardGame:
9708       case GNUChessGame:
9709       case PGNTag:
9710         /* Reached start of next game in file */
9711         if (appData.debugMode)
9712           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9713         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9714           case MT_NONE:
9715           case MT_CHECK:
9716             break;
9717           case MT_CHECKMATE:
9718           case MT_STAINMATE:
9719             if (WhiteOnMove(currentMove)) {
9720                 GameEnds(BlackWins, "Black mates", GE_FILE);
9721             } else {
9722                 GameEnds(WhiteWins, "White mates", GE_FILE);
9723             }
9724             break;
9725           case MT_STALEMATE:
9726             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9727             break;
9728         }
9729         done = TRUE;
9730         break;
9731
9732       case PositionDiagram:     /* should not happen; ignore */
9733       case ElapsedTime:         /* ignore */
9734       case NAG:                 /* ignore */
9735         if (appData.debugMode)
9736           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9737                   yy_text, (int) moveType);
9738         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9739
9740       case IllegalMove:
9741         if (appData.testLegality) {
9742             if (appData.debugMode)
9743               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9744             sprintf(move, _("Illegal move: %d.%s%s"),
9745                     (forwardMostMove / 2) + 1,
9746                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9747             DisplayError(move, 0);
9748             done = TRUE;
9749         } else {
9750             if (appData.debugMode)
9751               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9752                       yy_text, currentMoveString);
9753             fromX = currentMoveString[0] - AAA;
9754             fromY = currentMoveString[1] - ONE;
9755             toX = currentMoveString[2] - AAA;
9756             toY = currentMoveString[3] - ONE;
9757             promoChar = currentMoveString[4];
9758         }
9759         break;
9760
9761       case AmbiguousMove:
9762         if (appData.debugMode)
9763           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9764         sprintf(move, _("Ambiguous move: %d.%s%s"),
9765                 (forwardMostMove / 2) + 1,
9766                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9767         DisplayError(move, 0);
9768         done = TRUE;
9769         break;
9770
9771       default:
9772       case ImpossibleMove:
9773         if (appData.debugMode)
9774           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9775         sprintf(move, _("Illegal move: %d.%s%s"),
9776                 (forwardMostMove / 2) + 1,
9777                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9778         DisplayError(move, 0);
9779         done = TRUE;
9780         break;
9781     }
9782
9783     if (done) {
9784         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9785             DrawPosition(FALSE, boards[currentMove]);
9786             DisplayBothClocks();
9787             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9788               DisplayComment(currentMove - 1, commentList[currentMove]);
9789         }
9790         (void) StopLoadGameTimer();
9791         gameFileFP = NULL;
9792         cmailOldMove = forwardMostMove;
9793         return FALSE;
9794     } else {
9795         /* currentMoveString is set as a side-effect of yylex */
9796         strcat(currentMoveString, "\n");
9797         strcpy(moveList[forwardMostMove], currentMoveString);
9798         
9799         thinkOutput[0] = NULLCHAR;
9800         MakeMove(fromX, fromY, toX, toY, promoChar);
9801         currentMove = forwardMostMove;
9802         return TRUE;
9803     }
9804 }
9805
9806 /* Load the nth game from the given file */
9807 int
9808 LoadGameFromFile(filename, n, title, useList)
9809      char *filename;
9810      int n;
9811      char *title;
9812      /*Boolean*/ int useList;
9813 {
9814     FILE *f;
9815     char buf[MSG_SIZ];
9816
9817     if (strcmp(filename, "-") == 0) {
9818         f = stdin;
9819         title = "stdin";
9820     } else {
9821         f = fopen(filename, "rb");
9822         if (f == NULL) {
9823           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9824             DisplayError(buf, errno);
9825             return FALSE;
9826         }
9827     }
9828     if (fseek(f, 0, 0) == -1) {
9829         /* f is not seekable; probably a pipe */
9830         useList = FALSE;
9831     }
9832     if (useList && n == 0) {
9833         int error = GameListBuild(f);
9834         if (error) {
9835             DisplayError(_("Cannot build game list"), error);
9836         } else if (!ListEmpty(&gameList) &&
9837                    ((ListGame *) gameList.tailPred)->number > 1) {
9838             GameListPopUp(f, title);
9839             return TRUE;
9840         }
9841         GameListDestroy();
9842         n = 1;
9843     }
9844     if (n == 0) n = 1;
9845     return LoadGame(f, n, title, FALSE);
9846 }
9847
9848
9849 void
9850 MakeRegisteredMove()
9851 {
9852     int fromX, fromY, toX, toY;
9853     char promoChar;
9854     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9855         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9856           case CMAIL_MOVE:
9857           case CMAIL_DRAW:
9858             if (appData.debugMode)
9859               fprintf(debugFP, "Restoring %s for game %d\n",
9860                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9861     
9862             thinkOutput[0] = NULLCHAR;
9863             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9864             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9865             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9866             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9867             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9868             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9869             MakeMove(fromX, fromY, toX, toY, promoChar);
9870             ShowMove(fromX, fromY, toX, toY);
9871               
9872             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9873               case MT_NONE:
9874               case MT_CHECK:
9875                 break;
9876                 
9877               case MT_CHECKMATE:
9878               case MT_STAINMATE:
9879                 if (WhiteOnMove(currentMove)) {
9880                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9881                 } else {
9882                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9883                 }
9884                 break;
9885                 
9886               case MT_STALEMATE:
9887                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9888                 break;
9889             }
9890
9891             break;
9892             
9893           case CMAIL_RESIGN:
9894             if (WhiteOnMove(currentMove)) {
9895                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9896             } else {
9897                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9898             }
9899             break;
9900             
9901           case CMAIL_ACCEPT:
9902             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9903             break;
9904               
9905           default:
9906             break;
9907         }
9908     }
9909
9910     return;
9911 }
9912
9913 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9914 int
9915 CmailLoadGame(f, gameNumber, title, useList)
9916      FILE *f;
9917      int gameNumber;
9918      char *title;
9919      int useList;
9920 {
9921     int retVal;
9922
9923     if (gameNumber > nCmailGames) {
9924         DisplayError(_("No more games in this message"), 0);
9925         return FALSE;
9926     }
9927     if (f == lastLoadGameFP) {
9928         int offset = gameNumber - lastLoadGameNumber;
9929         if (offset == 0) {
9930             cmailMsg[0] = NULLCHAR;
9931             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9932                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9933                 nCmailMovesRegistered--;
9934             }
9935             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9936             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9937                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9938             }
9939         } else {
9940             if (! RegisterMove()) return FALSE;
9941         }
9942     }
9943
9944     retVal = LoadGame(f, gameNumber, title, useList);
9945
9946     /* Make move registered during previous look at this game, if any */
9947     MakeRegisteredMove();
9948
9949     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9950         commentList[currentMove]
9951           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9952         DisplayComment(currentMove - 1, commentList[currentMove]);
9953     }
9954
9955     return retVal;
9956 }
9957
9958 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9959 int
9960 ReloadGame(offset)
9961      int offset;
9962 {
9963     int gameNumber = lastLoadGameNumber + offset;
9964     if (lastLoadGameFP == NULL) {
9965         DisplayError(_("No game has been loaded yet"), 0);
9966         return FALSE;
9967     }
9968     if (gameNumber <= 0) {
9969         DisplayError(_("Can't back up any further"), 0);
9970         return FALSE;
9971     }
9972     if (cmailMsgLoaded) {
9973         return CmailLoadGame(lastLoadGameFP, gameNumber,
9974                              lastLoadGameTitle, lastLoadGameUseList);
9975     } else {
9976         return LoadGame(lastLoadGameFP, gameNumber,
9977                         lastLoadGameTitle, lastLoadGameUseList);
9978     }
9979 }
9980
9981
9982
9983 /* Load the nth game from open file f */
9984 int
9985 LoadGame(f, gameNumber, title, useList)
9986      FILE *f;
9987      int gameNumber;
9988      char *title;
9989      int useList;
9990 {
9991     ChessMove cm;
9992     char buf[MSG_SIZ];
9993     int gn = gameNumber;
9994     ListGame *lg = NULL;
9995     int numPGNTags = 0;
9996     int err;
9997     GameMode oldGameMode;
9998     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9999
10000     if (appData.debugMode) 
10001         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10002
10003     if (gameMode == Training )
10004         SetTrainingModeOff();
10005
10006     oldGameMode = gameMode;
10007     if (gameMode != BeginningOfGame) {
10008       Reset(FALSE, TRUE);
10009     }
10010
10011     gameFileFP = f;
10012     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10013         fclose(lastLoadGameFP);
10014     }
10015
10016     if (useList) {
10017         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10018         
10019         if (lg) {
10020             fseek(f, lg->offset, 0);
10021             GameListHighlight(gameNumber);
10022             gn = 1;
10023         }
10024         else {
10025             DisplayError(_("Game number out of range"), 0);
10026             return FALSE;
10027         }
10028     } else {
10029         GameListDestroy();
10030         if (fseek(f, 0, 0) == -1) {
10031             if (f == lastLoadGameFP ?
10032                 gameNumber == lastLoadGameNumber + 1 :
10033                 gameNumber == 1) {
10034                 gn = 1;
10035             } else {
10036                 DisplayError(_("Can't seek on game file"), 0);
10037                 return FALSE;
10038             }
10039         }
10040     }
10041     lastLoadGameFP = f;
10042     lastLoadGameNumber = gameNumber;
10043     strcpy(lastLoadGameTitle, title);
10044     lastLoadGameUseList = useList;
10045
10046     yynewfile(f);
10047
10048     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10049       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10050                 lg->gameInfo.black);
10051             DisplayTitle(buf);
10052     } else if (*title != NULLCHAR) {
10053         if (gameNumber > 1) {
10054             sprintf(buf, "%s %d", title, gameNumber);
10055             DisplayTitle(buf);
10056         } else {
10057             DisplayTitle(title);
10058         }
10059     }
10060
10061     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10062         gameMode = PlayFromGameFile;
10063         ModeHighlight();
10064     }
10065
10066     currentMove = forwardMostMove = backwardMostMove = 0;
10067     CopyBoard(boards[0], initialPosition);
10068     StopClocks();
10069
10070     /*
10071      * Skip the first gn-1 games in the file.
10072      * Also skip over anything that precedes an identifiable 
10073      * start of game marker, to avoid being confused by 
10074      * garbage at the start of the file.  Currently 
10075      * recognized start of game markers are the move number "1",
10076      * the pattern "gnuchess .* game", the pattern
10077      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10078      * A game that starts with one of the latter two patterns
10079      * will also have a move number 1, possibly
10080      * following a position diagram.
10081      * 5-4-02: Let's try being more lenient and allowing a game to
10082      * start with an unnumbered move.  Does that break anything?
10083      */
10084     cm = lastLoadGameStart = (ChessMove) 0;
10085     while (gn > 0) {
10086         yyboardindex = forwardMostMove;
10087         cm = (ChessMove) yylex();
10088         switch (cm) {
10089           case (ChessMove) 0:
10090             if (cmailMsgLoaded) {
10091                 nCmailGames = CMAIL_MAX_GAMES - gn;
10092             } else {
10093                 Reset(TRUE, TRUE);
10094                 DisplayError(_("Game not found in file"), 0);
10095             }
10096             return FALSE;
10097
10098           case GNUChessGame:
10099           case XBoardGame:
10100             gn--;
10101             lastLoadGameStart = cm;
10102             break;
10103             
10104           case MoveNumberOne:
10105             switch (lastLoadGameStart) {
10106               case GNUChessGame:
10107               case XBoardGame:
10108               case PGNTag:
10109                 break;
10110               case MoveNumberOne:
10111               case (ChessMove) 0:
10112                 gn--;           /* count this game */
10113                 lastLoadGameStart = cm;
10114                 break;
10115               default:
10116                 /* impossible */
10117                 break;
10118             }
10119             break;
10120
10121           case PGNTag:
10122             switch (lastLoadGameStart) {
10123               case GNUChessGame:
10124               case PGNTag:
10125               case MoveNumberOne:
10126               case (ChessMove) 0:
10127                 gn--;           /* count this game */
10128                 lastLoadGameStart = cm;
10129                 break;
10130               case XBoardGame:
10131                 lastLoadGameStart = cm; /* game counted already */
10132                 break;
10133               default:
10134                 /* impossible */
10135                 break;
10136             }
10137             if (gn > 0) {
10138                 do {
10139                     yyboardindex = forwardMostMove;
10140                     cm = (ChessMove) yylex();
10141                 } while (cm == PGNTag || cm == Comment);
10142             }
10143             break;
10144
10145           case WhiteWins:
10146           case BlackWins:
10147           case GameIsDrawn:
10148             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10149                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10150                     != CMAIL_OLD_RESULT) {
10151                     nCmailResults ++ ;
10152                     cmailResult[  CMAIL_MAX_GAMES
10153                                 - gn - 1] = CMAIL_OLD_RESULT;
10154                 }
10155             }
10156             break;
10157
10158           case NormalMove:
10159             /* Only a NormalMove can be at the start of a game
10160              * without a position diagram. */
10161             if (lastLoadGameStart == (ChessMove) 0) {
10162               gn--;
10163               lastLoadGameStart = MoveNumberOne;
10164             }
10165             break;
10166
10167           default:
10168             break;
10169         }
10170     }
10171     
10172     if (appData.debugMode)
10173       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10174
10175     if (cm == XBoardGame) {
10176         /* Skip any header junk before position diagram and/or move 1 */
10177         for (;;) {
10178             yyboardindex = forwardMostMove;
10179             cm = (ChessMove) yylex();
10180
10181             if (cm == (ChessMove) 0 ||
10182                 cm == GNUChessGame || cm == XBoardGame) {
10183                 /* Empty game; pretend end-of-file and handle later */
10184                 cm = (ChessMove) 0;
10185                 break;
10186             }
10187
10188             if (cm == MoveNumberOne || cm == PositionDiagram ||
10189                 cm == PGNTag || cm == Comment)
10190               break;
10191         }
10192     } else if (cm == GNUChessGame) {
10193         if (gameInfo.event != NULL) {
10194             free(gameInfo.event);
10195         }
10196         gameInfo.event = StrSave(yy_text);
10197     }   
10198
10199     startedFromSetupPosition = FALSE;
10200     while (cm == PGNTag) {
10201         if (appData.debugMode) 
10202           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10203         err = ParsePGNTag(yy_text, &gameInfo);
10204         if (!err) numPGNTags++;
10205
10206         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10207         if(gameInfo.variant != oldVariant) {
10208             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10209             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10210             InitPosition(TRUE);
10211             oldVariant = gameInfo.variant;
10212             if (appData.debugMode) 
10213               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10214         }
10215
10216
10217         if (gameInfo.fen != NULL) {
10218           Board initial_position;
10219           startedFromSetupPosition = TRUE;
10220           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10221             Reset(TRUE, TRUE);
10222             DisplayError(_("Bad FEN position in file"), 0);
10223             return FALSE;
10224           }
10225           CopyBoard(boards[0], initial_position);
10226           if (blackPlaysFirst) {
10227             currentMove = forwardMostMove = backwardMostMove = 1;
10228             CopyBoard(boards[1], initial_position);
10229             strcpy(moveList[0], "");
10230             strcpy(parseList[0], "");
10231             timeRemaining[0][1] = whiteTimeRemaining;
10232             timeRemaining[1][1] = blackTimeRemaining;
10233             if (commentList[0] != NULL) {
10234               commentList[1] = commentList[0];
10235               commentList[0] = NULL;
10236             }
10237           } else {
10238             currentMove = forwardMostMove = backwardMostMove = 0;
10239           }
10240           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10241           {   int i;
10242               initialRulePlies = FENrulePlies;
10243               for( i=0; i< nrCastlingRights; i++ )
10244                   initialRights[i] = initial_position[CASTLING][i];
10245           }
10246           yyboardindex = forwardMostMove;
10247           free(gameInfo.fen);
10248           gameInfo.fen = NULL;
10249         }
10250
10251         yyboardindex = forwardMostMove;
10252         cm = (ChessMove) yylex();
10253
10254         /* Handle comments interspersed among the tags */
10255         while (cm == Comment) {
10256             char *p;
10257             if (appData.debugMode) 
10258               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10259             p = yy_text;
10260             AppendComment(currentMove, p, FALSE);
10261             yyboardindex = forwardMostMove;
10262             cm = (ChessMove) yylex();
10263         }
10264     }
10265
10266     /* don't rely on existence of Event tag since if game was
10267      * pasted from clipboard the Event tag may not exist
10268      */
10269     if (numPGNTags > 0){
10270         char *tags;
10271         if (gameInfo.variant == VariantNormal) {
10272           VariantClass v = StringToVariant(gameInfo.event);
10273           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10274           if(v < VariantShogi) gameInfo.variant = v;
10275         }
10276         if (!matchMode) {
10277           if( appData.autoDisplayTags ) {
10278             tags = PGNTags(&gameInfo);
10279             TagsPopUp(tags, CmailMsg());
10280             free(tags);
10281           }
10282         }
10283     } else {
10284         /* Make something up, but don't display it now */
10285         SetGameInfo();
10286         TagsPopDown();
10287     }
10288
10289     if (cm == PositionDiagram) {
10290         int i, j;
10291         char *p;
10292         Board initial_position;
10293
10294         if (appData.debugMode)
10295           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10296
10297         if (!startedFromSetupPosition) {
10298             p = yy_text;
10299             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10300               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10301                 switch (*p) {
10302                   case '[':
10303                   case '-':
10304                   case ' ':
10305                   case '\t':
10306                   case '\n':
10307                   case '\r':
10308                     break;
10309                   default:
10310                     initial_position[i][j++] = CharToPiece(*p);
10311                     break;
10312                 }
10313             while (*p == ' ' || *p == '\t' ||
10314                    *p == '\n' || *p == '\r') p++;
10315         
10316             if (strncmp(p, "black", strlen("black"))==0)
10317               blackPlaysFirst = TRUE;
10318             else
10319               blackPlaysFirst = FALSE;
10320             startedFromSetupPosition = TRUE;
10321         
10322             CopyBoard(boards[0], initial_position);
10323             if (blackPlaysFirst) {
10324                 currentMove = forwardMostMove = backwardMostMove = 1;
10325                 CopyBoard(boards[1], initial_position);
10326                 strcpy(moveList[0], "");
10327                 strcpy(parseList[0], "");
10328                 timeRemaining[0][1] = whiteTimeRemaining;
10329                 timeRemaining[1][1] = blackTimeRemaining;
10330                 if (commentList[0] != NULL) {
10331                     commentList[1] = commentList[0];
10332                     commentList[0] = NULL;
10333                 }
10334             } else {
10335                 currentMove = forwardMostMove = backwardMostMove = 0;
10336             }
10337         }
10338         yyboardindex = forwardMostMove;
10339         cm = (ChessMove) yylex();
10340     }
10341
10342     if (first.pr == NoProc) {
10343         StartChessProgram(&first);
10344     }
10345     InitChessProgram(&first, FALSE);
10346     SendToProgram("force\n", &first);
10347     if (startedFromSetupPosition) {
10348         SendBoard(&first, forwardMostMove);
10349     if (appData.debugMode) {
10350         fprintf(debugFP, "Load Game\n");
10351     }
10352         DisplayBothClocks();
10353     }      
10354
10355     /* [HGM] server: flag to write setup moves in broadcast file as one */
10356     loadFlag = appData.suppressLoadMoves;
10357
10358     while (cm == Comment) {
10359         char *p;
10360         if (appData.debugMode) 
10361           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10362         p = yy_text;
10363         AppendComment(currentMove, p, FALSE);
10364         yyboardindex = forwardMostMove;
10365         cm = (ChessMove) yylex();
10366     }
10367
10368     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10369         cm == WhiteWins || cm == BlackWins ||
10370         cm == GameIsDrawn || cm == GameUnfinished) {
10371         DisplayMessage("", _("No moves in game"));
10372         if (cmailMsgLoaded) {
10373             if (appData.debugMode)
10374               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10375             ClearHighlights();
10376             flipView = FALSE;
10377         }
10378         DrawPosition(FALSE, boards[currentMove]);
10379         DisplayBothClocks();
10380         gameMode = EditGame;
10381         ModeHighlight();
10382         gameFileFP = NULL;
10383         cmailOldMove = 0;
10384         return TRUE;
10385     }
10386
10387     // [HGM] PV info: routine tests if comment empty
10388     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10389         DisplayComment(currentMove - 1, commentList[currentMove]);
10390     }
10391     if (!matchMode && appData.timeDelay != 0) 
10392       DrawPosition(FALSE, boards[currentMove]);
10393
10394     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10395       programStats.ok_to_send = 1;
10396     }
10397
10398     /* if the first token after the PGN tags is a move
10399      * and not move number 1, retrieve it from the parser 
10400      */
10401     if (cm != MoveNumberOne)
10402         LoadGameOneMove(cm);
10403
10404     /* load the remaining moves from the file */
10405     while (LoadGameOneMove((ChessMove)0)) {
10406       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10407       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10408     }
10409
10410     /* rewind to the start of the game */
10411     currentMove = backwardMostMove;
10412
10413     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10414
10415     if (oldGameMode == AnalyzeFile ||
10416         oldGameMode == AnalyzeMode) {
10417       AnalyzeFileEvent();
10418     }
10419
10420     if (matchMode || appData.timeDelay == 0) {
10421       ToEndEvent();
10422       gameMode = EditGame;
10423       ModeHighlight();
10424     } else if (appData.timeDelay > 0) {
10425       AutoPlayGameLoop();
10426     }
10427
10428     if (appData.debugMode) 
10429         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10430
10431     loadFlag = 0; /* [HGM] true game starts */
10432     return TRUE;
10433 }
10434
10435 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10436 int
10437 ReloadPosition(offset)
10438      int offset;
10439 {
10440     int positionNumber = lastLoadPositionNumber + offset;
10441     if (lastLoadPositionFP == NULL) {
10442         DisplayError(_("No position has been loaded yet"), 0);
10443         return FALSE;
10444     }
10445     if (positionNumber <= 0) {
10446         DisplayError(_("Can't back up any further"), 0);
10447         return FALSE;
10448     }
10449     return LoadPosition(lastLoadPositionFP, positionNumber,
10450                         lastLoadPositionTitle);
10451 }
10452
10453 /* Load the nth position from the given file */
10454 int
10455 LoadPositionFromFile(filename, n, title)
10456      char *filename;
10457      int n;
10458      char *title;
10459 {
10460     FILE *f;
10461     char buf[MSG_SIZ];
10462
10463     if (strcmp(filename, "-") == 0) {
10464         return LoadPosition(stdin, n, "stdin");
10465     } else {
10466         f = fopen(filename, "rb");
10467         if (f == NULL) {
10468             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10469             DisplayError(buf, errno);
10470             return FALSE;
10471         } else {
10472             return LoadPosition(f, n, title);
10473         }
10474     }
10475 }
10476
10477 /* Load the nth position from the given open file, and close it */
10478 int
10479 LoadPosition(f, positionNumber, title)
10480      FILE *f;
10481      int positionNumber;
10482      char *title;
10483 {
10484     char *p, line[MSG_SIZ];
10485     Board initial_position;
10486     int i, j, fenMode, pn;
10487     
10488     if (gameMode == Training )
10489         SetTrainingModeOff();
10490
10491     if (gameMode != BeginningOfGame) {
10492         Reset(FALSE, TRUE);
10493     }
10494     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10495         fclose(lastLoadPositionFP);
10496     }
10497     if (positionNumber == 0) positionNumber = 1;
10498     lastLoadPositionFP = f;
10499     lastLoadPositionNumber = positionNumber;
10500     strcpy(lastLoadPositionTitle, title);
10501     if (first.pr == NoProc) {
10502       StartChessProgram(&first);
10503       InitChessProgram(&first, FALSE);
10504     }    
10505     pn = positionNumber;
10506     if (positionNumber < 0) {
10507         /* Negative position number means to seek to that byte offset */
10508         if (fseek(f, -positionNumber, 0) == -1) {
10509             DisplayError(_("Can't seek on position file"), 0);
10510             return FALSE;
10511         };
10512         pn = 1;
10513     } else {
10514         if (fseek(f, 0, 0) == -1) {
10515             if (f == lastLoadPositionFP ?
10516                 positionNumber == lastLoadPositionNumber + 1 :
10517                 positionNumber == 1) {
10518                 pn = 1;
10519             } else {
10520                 DisplayError(_("Can't seek on position file"), 0);
10521                 return FALSE;
10522             }
10523         }
10524     }
10525     /* See if this file is FEN or old-style xboard */
10526     if (fgets(line, MSG_SIZ, f) == NULL) {
10527         DisplayError(_("Position not found in file"), 0);
10528         return FALSE;
10529     }
10530     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10531     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10532
10533     if (pn >= 2) {
10534         if (fenMode || line[0] == '#') pn--;
10535         while (pn > 0) {
10536             /* skip positions before number pn */
10537             if (fgets(line, MSG_SIZ, f) == NULL) {
10538                 Reset(TRUE, TRUE);
10539                 DisplayError(_("Position not found in file"), 0);
10540                 return FALSE;
10541             }
10542             if (fenMode || line[0] == '#') pn--;
10543         }
10544     }
10545
10546     if (fenMode) {
10547         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10548             DisplayError(_("Bad FEN position in file"), 0);
10549             return FALSE;
10550         }
10551     } else {
10552         (void) fgets(line, MSG_SIZ, f);
10553         (void) fgets(line, MSG_SIZ, f);
10554     
10555         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10556             (void) fgets(line, MSG_SIZ, f);
10557             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10558                 if (*p == ' ')
10559                   continue;
10560                 initial_position[i][j++] = CharToPiece(*p);
10561             }
10562         }
10563     
10564         blackPlaysFirst = FALSE;
10565         if (!feof(f)) {
10566             (void) fgets(line, MSG_SIZ, f);
10567             if (strncmp(line, "black", strlen("black"))==0)
10568               blackPlaysFirst = TRUE;
10569         }
10570     }
10571     startedFromSetupPosition = TRUE;
10572     
10573     SendToProgram("force\n", &first);
10574     CopyBoard(boards[0], initial_position);
10575     if (blackPlaysFirst) {
10576         currentMove = forwardMostMove = backwardMostMove = 1;
10577         strcpy(moveList[0], "");
10578         strcpy(parseList[0], "");
10579         CopyBoard(boards[1], initial_position);
10580         DisplayMessage("", _("Black to play"));
10581     } else {
10582         currentMove = forwardMostMove = backwardMostMove = 0;
10583         DisplayMessage("", _("White to play"));
10584     }
10585     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10586     SendBoard(&first, forwardMostMove);
10587     if (appData.debugMode) {
10588 int i, j;
10589   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10590   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10591         fprintf(debugFP, "Load Position\n");
10592     }
10593
10594     if (positionNumber > 1) {
10595         sprintf(line, "%s %d", title, positionNumber);
10596         DisplayTitle(line);
10597     } else {
10598         DisplayTitle(title);
10599     }
10600     gameMode = EditGame;
10601     ModeHighlight();
10602     ResetClocks();
10603     timeRemaining[0][1] = whiteTimeRemaining;
10604     timeRemaining[1][1] = blackTimeRemaining;
10605     DrawPosition(FALSE, boards[currentMove]);
10606    
10607     return TRUE;
10608 }
10609
10610
10611 void
10612 CopyPlayerNameIntoFileName(dest, src)
10613      char **dest, *src;
10614 {
10615     while (*src != NULLCHAR && *src != ',') {
10616         if (*src == ' ') {
10617             *(*dest)++ = '_';
10618             src++;
10619         } else {
10620             *(*dest)++ = *src++;
10621         }
10622     }
10623 }
10624
10625 char *DefaultFileName(ext)
10626      char *ext;
10627 {
10628     static char def[MSG_SIZ];
10629     char *p;
10630
10631     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10632         p = def;
10633         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10634         *p++ = '-';
10635         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10636         *p++ = '.';
10637         strcpy(p, ext);
10638     } else {
10639         def[0] = NULLCHAR;
10640     }
10641     return def;
10642 }
10643
10644 /* Save the current game to the given file */
10645 int
10646 SaveGameToFile(filename, append)
10647      char *filename;
10648      int append;
10649 {
10650     FILE *f;
10651     char buf[MSG_SIZ];
10652
10653     if (strcmp(filename, "-") == 0) {
10654         return SaveGame(stdout, 0, NULL);
10655     } else {
10656         f = fopen(filename, append ? "a" : "w");
10657         if (f == NULL) {
10658             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10659             DisplayError(buf, errno);
10660             return FALSE;
10661         } else {
10662             return SaveGame(f, 0, NULL);
10663         }
10664     }
10665 }
10666
10667 char *
10668 SavePart(str)
10669      char *str;
10670 {
10671     static char buf[MSG_SIZ];
10672     char *p;
10673     
10674     p = strchr(str, ' ');
10675     if (p == NULL) return str;
10676     strncpy(buf, str, p - str);
10677     buf[p - str] = NULLCHAR;
10678     return buf;
10679 }
10680
10681 #define PGN_MAX_LINE 75
10682
10683 #define PGN_SIDE_WHITE  0
10684 #define PGN_SIDE_BLACK  1
10685
10686 /* [AS] */
10687 static int FindFirstMoveOutOfBook( int side )
10688 {
10689     int result = -1;
10690
10691     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10692         int index = backwardMostMove;
10693         int has_book_hit = 0;
10694
10695         if( (index % 2) != side ) {
10696             index++;
10697         }
10698
10699         while( index < forwardMostMove ) {
10700             /* Check to see if engine is in book */
10701             int depth = pvInfoList[index].depth;
10702             int score = pvInfoList[index].score;
10703             int in_book = 0;
10704
10705             if( depth <= 2 ) {
10706                 in_book = 1;
10707             }
10708             else if( score == 0 && depth == 63 ) {
10709                 in_book = 1; /* Zappa */
10710             }
10711             else if( score == 2 && depth == 99 ) {
10712                 in_book = 1; /* Abrok */
10713             }
10714
10715             has_book_hit += in_book;
10716
10717             if( ! in_book ) {
10718                 result = index;
10719
10720                 break;
10721             }
10722
10723             index += 2;
10724         }
10725     }
10726
10727     return result;
10728 }
10729
10730 /* [AS] */
10731 void GetOutOfBookInfo( char * buf )
10732 {
10733     int oob[2];
10734     int i;
10735     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10736
10737     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10738     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10739
10740     *buf = '\0';
10741
10742     if( oob[0] >= 0 || oob[1] >= 0 ) {
10743         for( i=0; i<2; i++ ) {
10744             int idx = oob[i];
10745
10746             if( idx >= 0 ) {
10747                 if( i > 0 && oob[0] >= 0 ) {
10748                     strcat( buf, "   " );
10749                 }
10750
10751                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10752                 sprintf( buf+strlen(buf), "%s%.2f", 
10753                     pvInfoList[idx].score >= 0 ? "+" : "",
10754                     pvInfoList[idx].score / 100.0 );
10755             }
10756         }
10757     }
10758 }
10759
10760 /* Save game in PGN style and close the file */
10761 int
10762 SaveGamePGN(f)
10763      FILE *f;
10764 {
10765     int i, offset, linelen, newblock;
10766     time_t tm;
10767 //    char *movetext;
10768     char numtext[32];
10769     int movelen, numlen, blank;
10770     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10771
10772     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10773     
10774     tm = time((time_t *) NULL);
10775     
10776     PrintPGNTags(f, &gameInfo);
10777     
10778     if (backwardMostMove > 0 || startedFromSetupPosition) {
10779         char *fen = PositionToFEN(backwardMostMove, NULL);
10780         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10781         fprintf(f, "\n{--------------\n");
10782         PrintPosition(f, backwardMostMove);
10783         fprintf(f, "--------------}\n");
10784         free(fen);
10785     }
10786     else {
10787         /* [AS] Out of book annotation */
10788         if( appData.saveOutOfBookInfo ) {
10789             char buf[64];
10790
10791             GetOutOfBookInfo( buf );
10792
10793             if( buf[0] != '\0' ) {
10794                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10795             }
10796         }
10797
10798         fprintf(f, "\n");
10799     }
10800
10801     i = backwardMostMove;
10802     linelen = 0;
10803     newblock = TRUE;
10804
10805     while (i < forwardMostMove) {
10806         /* Print comments preceding this move */
10807         if (commentList[i] != NULL) {
10808             if (linelen > 0) fprintf(f, "\n");
10809             fprintf(f, "%s", commentList[i]);
10810             linelen = 0;
10811             newblock = TRUE;
10812         }
10813
10814         /* Format move number */
10815         if ((i % 2) == 0) {
10816             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10817         } else {
10818             if (newblock) {
10819                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10820             } else {
10821                 numtext[0] = NULLCHAR;
10822             }
10823         }
10824         numlen = strlen(numtext);
10825         newblock = FALSE;
10826
10827         /* Print move number */
10828         blank = linelen > 0 && numlen > 0;
10829         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10830             fprintf(f, "\n");
10831             linelen = 0;
10832             blank = 0;
10833         }
10834         if (blank) {
10835             fprintf(f, " ");
10836             linelen++;
10837         }
10838         fprintf(f, "%s", numtext);
10839         linelen += numlen;
10840
10841         /* Get move */
10842         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10843         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10844
10845         /* Print move */
10846         blank = linelen > 0 && movelen > 0;
10847         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10848             fprintf(f, "\n");
10849             linelen = 0;
10850             blank = 0;
10851         }
10852         if (blank) {
10853             fprintf(f, " ");
10854             linelen++;
10855         }
10856         fprintf(f, "%s", move_buffer);
10857         linelen += movelen;
10858
10859         /* [AS] Add PV info if present */
10860         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10861             /* [HGM] add time */
10862             char buf[MSG_SIZ]; int seconds;
10863
10864             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10865
10866             if( seconds <= 0) buf[0] = 0; else
10867             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10868                 seconds = (seconds + 4)/10; // round to full seconds
10869                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10870                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10871             }
10872
10873             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10874                 pvInfoList[i].score >= 0 ? "+" : "",
10875                 pvInfoList[i].score / 100.0,
10876                 pvInfoList[i].depth,
10877                 buf );
10878
10879             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10880
10881             /* Print score/depth */
10882             blank = linelen > 0 && movelen > 0;
10883             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10884                 fprintf(f, "\n");
10885                 linelen = 0;
10886                 blank = 0;
10887             }
10888             if (blank) {
10889                 fprintf(f, " ");
10890                 linelen++;
10891             }
10892             fprintf(f, "%s", move_buffer);
10893             linelen += movelen;
10894         }
10895
10896         i++;
10897     }
10898     
10899     /* Start a new line */
10900     if (linelen > 0) fprintf(f, "\n");
10901
10902     /* Print comments after last move */
10903     if (commentList[i] != NULL) {
10904         fprintf(f, "%s\n", commentList[i]);
10905     }
10906
10907     /* Print result */
10908     if (gameInfo.resultDetails != NULL &&
10909         gameInfo.resultDetails[0] != NULLCHAR) {
10910         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10911                 PGNResult(gameInfo.result));
10912     } else {
10913         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10914     }
10915
10916     fclose(f);
10917     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10918     return TRUE;
10919 }
10920
10921 /* Save game in old style and close the file */
10922 int
10923 SaveGameOldStyle(f)
10924      FILE *f;
10925 {
10926     int i, offset;
10927     time_t tm;
10928     
10929     tm = time((time_t *) NULL);
10930     
10931     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10932     PrintOpponents(f);
10933     
10934     if (backwardMostMove > 0 || startedFromSetupPosition) {
10935         fprintf(f, "\n[--------------\n");
10936         PrintPosition(f, backwardMostMove);
10937         fprintf(f, "--------------]\n");
10938     } else {
10939         fprintf(f, "\n");
10940     }
10941
10942     i = backwardMostMove;
10943     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10944
10945     while (i < forwardMostMove) {
10946         if (commentList[i] != NULL) {
10947             fprintf(f, "[%s]\n", commentList[i]);
10948         }
10949
10950         if ((i % 2) == 1) {
10951             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10952             i++;
10953         } else {
10954             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10955             i++;
10956             if (commentList[i] != NULL) {
10957                 fprintf(f, "\n");
10958                 continue;
10959             }
10960             if (i >= forwardMostMove) {
10961                 fprintf(f, "\n");
10962                 break;
10963             }
10964             fprintf(f, "%s\n", parseList[i]);
10965             i++;
10966         }
10967     }
10968     
10969     if (commentList[i] != NULL) {
10970         fprintf(f, "[%s]\n", commentList[i]);
10971     }
10972
10973     /* This isn't really the old style, but it's close enough */
10974     if (gameInfo.resultDetails != NULL &&
10975         gameInfo.resultDetails[0] != NULLCHAR) {
10976         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10977                 gameInfo.resultDetails);
10978     } else {
10979         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10980     }
10981
10982     fclose(f);
10983     return TRUE;
10984 }
10985
10986 /* Save the current game to open file f and close the file */
10987 int
10988 SaveGame(f, dummy, dummy2)
10989      FILE *f;
10990      int dummy;
10991      char *dummy2;
10992 {
10993     if (gameMode == EditPosition) EditPositionDone(TRUE);
10994     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10995     if (appData.oldSaveStyle)
10996       return SaveGameOldStyle(f);
10997     else
10998       return SaveGamePGN(f);
10999 }
11000
11001 /* Save the current position to the given file */
11002 int
11003 SavePositionToFile(filename)
11004      char *filename;
11005 {
11006     FILE *f;
11007     char buf[MSG_SIZ];
11008
11009     if (strcmp(filename, "-") == 0) {
11010         return SavePosition(stdout, 0, NULL);
11011     } else {
11012         f = fopen(filename, "a");
11013         if (f == NULL) {
11014             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11015             DisplayError(buf, errno);
11016             return FALSE;
11017         } else {
11018             SavePosition(f, 0, NULL);
11019             return TRUE;
11020         }
11021     }
11022 }
11023
11024 /* Save the current position to the given open file and close the file */
11025 int
11026 SavePosition(f, dummy, dummy2)
11027      FILE *f;
11028      int dummy;
11029      char *dummy2;
11030 {
11031     time_t tm;
11032     char *fen;
11033     
11034     if (gameMode == EditPosition) EditPositionDone(TRUE);
11035     if (appData.oldSaveStyle) {
11036         tm = time((time_t *) NULL);
11037     
11038         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11039         PrintOpponents(f);
11040         fprintf(f, "[--------------\n");
11041         PrintPosition(f, currentMove);
11042         fprintf(f, "--------------]\n");
11043     } else {
11044         fen = PositionToFEN(currentMove, NULL);
11045         fprintf(f, "%s\n", fen);
11046         free(fen);
11047     }
11048     fclose(f);
11049     return TRUE;
11050 }
11051
11052 void
11053 ReloadCmailMsgEvent(unregister)
11054      int unregister;
11055 {
11056 #if !WIN32
11057     static char *inFilename = NULL;
11058     static char *outFilename;
11059     int i;
11060     struct stat inbuf, outbuf;
11061     int status;
11062     
11063     /* Any registered moves are unregistered if unregister is set, */
11064     /* i.e. invoked by the signal handler */
11065     if (unregister) {
11066         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11067             cmailMoveRegistered[i] = FALSE;
11068             if (cmailCommentList[i] != NULL) {
11069                 free(cmailCommentList[i]);
11070                 cmailCommentList[i] = NULL;
11071             }
11072         }
11073         nCmailMovesRegistered = 0;
11074     }
11075
11076     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11077         cmailResult[i] = CMAIL_NOT_RESULT;
11078     }
11079     nCmailResults = 0;
11080
11081     if (inFilename == NULL) {
11082         /* Because the filenames are static they only get malloced once  */
11083         /* and they never get freed                                      */
11084         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11085         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11086
11087         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11088         sprintf(outFilename, "%s.out", appData.cmailGameName);
11089     }
11090     
11091     status = stat(outFilename, &outbuf);
11092     if (status < 0) {
11093         cmailMailedMove = FALSE;
11094     } else {
11095         status = stat(inFilename, &inbuf);
11096         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11097     }
11098     
11099     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11100        counts the games, notes how each one terminated, etc.
11101        
11102        It would be nice to remove this kludge and instead gather all
11103        the information while building the game list.  (And to keep it
11104        in the game list nodes instead of having a bunch of fixed-size
11105        parallel arrays.)  Note this will require getting each game's
11106        termination from the PGN tags, as the game list builder does
11107        not process the game moves.  --mann
11108        */
11109     cmailMsgLoaded = TRUE;
11110     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11111     
11112     /* Load first game in the file or popup game menu */
11113     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11114
11115 #endif /* !WIN32 */
11116     return;
11117 }
11118
11119 int
11120 RegisterMove()
11121 {
11122     FILE *f;
11123     char string[MSG_SIZ];
11124
11125     if (   cmailMailedMove
11126         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11127         return TRUE;            /* Allow free viewing  */
11128     }
11129
11130     /* Unregister move to ensure that we don't leave RegisterMove        */
11131     /* with the move registered when the conditions for registering no   */
11132     /* longer hold                                                       */
11133     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11134         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11135         nCmailMovesRegistered --;
11136
11137         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11138           {
11139               free(cmailCommentList[lastLoadGameNumber - 1]);
11140               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11141           }
11142     }
11143
11144     if (cmailOldMove == -1) {
11145         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11146         return FALSE;
11147     }
11148
11149     if (currentMove > cmailOldMove + 1) {
11150         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11151         return FALSE;
11152     }
11153
11154     if (currentMove < cmailOldMove) {
11155         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11156         return FALSE;
11157     }
11158
11159     if (forwardMostMove > currentMove) {
11160         /* Silently truncate extra moves */
11161         TruncateGame();
11162     }
11163
11164     if (   (currentMove == cmailOldMove + 1)
11165         || (   (currentMove == cmailOldMove)
11166             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11167                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11168         if (gameInfo.result != GameUnfinished) {
11169             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11170         }
11171
11172         if (commentList[currentMove] != NULL) {
11173             cmailCommentList[lastLoadGameNumber - 1]
11174               = StrSave(commentList[currentMove]);
11175         }
11176         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11177
11178         if (appData.debugMode)
11179           fprintf(debugFP, "Saving %s for game %d\n",
11180                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11181
11182         sprintf(string,
11183                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11184         
11185         f = fopen(string, "w");
11186         if (appData.oldSaveStyle) {
11187             SaveGameOldStyle(f); /* also closes the file */
11188             
11189             sprintf(string, "%s.pos.out", appData.cmailGameName);
11190             f = fopen(string, "w");
11191             SavePosition(f, 0, NULL); /* also closes the file */
11192         } else {
11193             fprintf(f, "{--------------\n");
11194             PrintPosition(f, currentMove);
11195             fprintf(f, "--------------}\n\n");
11196             
11197             SaveGame(f, 0, NULL); /* also closes the file*/
11198         }
11199         
11200         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11201         nCmailMovesRegistered ++;
11202     } else if (nCmailGames == 1) {
11203         DisplayError(_("You have not made a move yet"), 0);
11204         return FALSE;
11205     }
11206
11207     return TRUE;
11208 }
11209
11210 void
11211 MailMoveEvent()
11212 {
11213 #if !WIN32
11214     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11215     FILE *commandOutput;
11216     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11217     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11218     int nBuffers;
11219     int i;
11220     int archived;
11221     char *arcDir;
11222
11223     if (! cmailMsgLoaded) {
11224         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11225         return;
11226     }
11227
11228     if (nCmailGames == nCmailResults) {
11229         DisplayError(_("No unfinished games"), 0);
11230         return;
11231     }
11232
11233 #if CMAIL_PROHIBIT_REMAIL
11234     if (cmailMailedMove) {
11235         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);
11236         DisplayError(msg, 0);
11237         return;
11238     }
11239 #endif
11240
11241     if (! (cmailMailedMove || RegisterMove())) return;
11242     
11243     if (   cmailMailedMove
11244         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11245         sprintf(string, partCommandString,
11246                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11247         commandOutput = popen(string, "r");
11248
11249         if (commandOutput == NULL) {
11250             DisplayError(_("Failed to invoke cmail"), 0);
11251         } else {
11252             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11253                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11254             }
11255             if (nBuffers > 1) {
11256                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11257                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11258                 nBytes = MSG_SIZ - 1;
11259             } else {
11260                 (void) memcpy(msg, buffer, nBytes);
11261             }
11262             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11263
11264             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11265                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11266
11267                 archived = TRUE;
11268                 for (i = 0; i < nCmailGames; i ++) {
11269                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11270                         archived = FALSE;
11271                     }
11272                 }
11273                 if (   archived
11274                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11275                         != NULL)) {
11276                     sprintf(buffer, "%s/%s.%s.archive",
11277                             arcDir,
11278                             appData.cmailGameName,
11279                             gameInfo.date);
11280                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11281                     cmailMsgLoaded = FALSE;
11282                 }
11283             }
11284
11285             DisplayInformation(msg);
11286             pclose(commandOutput);
11287         }
11288     } else {
11289         if ((*cmailMsg) != '\0') {
11290             DisplayInformation(cmailMsg);
11291         }
11292     }
11293
11294     return;
11295 #endif /* !WIN32 */
11296 }
11297
11298 char *
11299 CmailMsg()
11300 {
11301 #if WIN32
11302     return NULL;
11303 #else
11304     int  prependComma = 0;
11305     char number[5];
11306     char string[MSG_SIZ];       /* Space for game-list */
11307     int  i;
11308     
11309     if (!cmailMsgLoaded) return "";
11310
11311     if (cmailMailedMove) {
11312         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11313     } else {
11314         /* Create a list of games left */
11315         sprintf(string, "[");
11316         for (i = 0; i < nCmailGames; i ++) {
11317             if (! (   cmailMoveRegistered[i]
11318                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11319                 if (prependComma) {
11320                     sprintf(number, ",%d", i + 1);
11321                 } else {
11322                     sprintf(number, "%d", i + 1);
11323                     prependComma = 1;
11324                 }
11325                 
11326                 strcat(string, number);
11327             }
11328         }
11329         strcat(string, "]");
11330
11331         if (nCmailMovesRegistered + nCmailResults == 0) {
11332             switch (nCmailGames) {
11333               case 1:
11334                 sprintf(cmailMsg,
11335                         _("Still need to make move for game\n"));
11336                 break;
11337                 
11338               case 2:
11339                 sprintf(cmailMsg,
11340                         _("Still need to make moves for both games\n"));
11341                 break;
11342                 
11343               default:
11344                 sprintf(cmailMsg,
11345                         _("Still need to make moves for all %d games\n"),
11346                         nCmailGames);
11347                 break;
11348             }
11349         } else {
11350             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11351               case 1:
11352                 sprintf(cmailMsg,
11353                         _("Still need to make a move for game %s\n"),
11354                         string);
11355                 break;
11356                 
11357               case 0:
11358                 if (nCmailResults == nCmailGames) {
11359                     sprintf(cmailMsg, _("No unfinished games\n"));
11360                 } else {
11361                     sprintf(cmailMsg, _("Ready to send mail\n"));
11362                 }
11363                 break;
11364                 
11365               default:
11366                 sprintf(cmailMsg,
11367                         _("Still need to make moves for games %s\n"),
11368                         string);
11369             }
11370         }
11371     }
11372     return cmailMsg;
11373 #endif /* WIN32 */
11374 }
11375
11376 void
11377 ResetGameEvent()
11378 {
11379     if (gameMode == Training)
11380       SetTrainingModeOff();
11381
11382     Reset(TRUE, TRUE);
11383     cmailMsgLoaded = FALSE;
11384     if (appData.icsActive) {
11385       SendToICS(ics_prefix);
11386       SendToICS("refresh\n");
11387     }
11388 }
11389
11390 void
11391 ExitEvent(status)
11392      int status;
11393 {
11394     exiting++;
11395     if (exiting > 2) {
11396       /* Give up on clean exit */
11397       exit(status);
11398     }
11399     if (exiting > 1) {
11400       /* Keep trying for clean exit */
11401       return;
11402     }
11403
11404     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11405
11406     if (telnetISR != NULL) {
11407       RemoveInputSource(telnetISR);
11408     }
11409     if (icsPR != NoProc) {
11410       DestroyChildProcess(icsPR, TRUE);
11411     }
11412
11413     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11414     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11415
11416     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11417     /* make sure this other one finishes before killing it!                  */
11418     if(endingGame) { int count = 0;
11419         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11420         while(endingGame && count++ < 10) DoSleep(1);
11421         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11422     }
11423
11424     /* Kill off chess programs */
11425     if (first.pr != NoProc) {
11426         ExitAnalyzeMode();
11427         
11428         DoSleep( appData.delayBeforeQuit );
11429         SendToProgram("quit\n", &first);
11430         DoSleep( appData.delayAfterQuit );
11431         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11432     }
11433     if (second.pr != NoProc) {
11434         DoSleep( appData.delayBeforeQuit );
11435         SendToProgram("quit\n", &second);
11436         DoSleep( appData.delayAfterQuit );
11437         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11438     }
11439     if (first.isr != NULL) {
11440         RemoveInputSource(first.isr);
11441     }
11442     if (second.isr != NULL) {
11443         RemoveInputSource(second.isr);
11444     }
11445
11446     ShutDownFrontEnd();
11447     exit(status);
11448 }
11449
11450 void
11451 PauseEvent()
11452 {
11453     if (appData.debugMode)
11454         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11455     if (pausing) {
11456         pausing = FALSE;
11457         ModeHighlight();
11458         if (gameMode == MachinePlaysWhite ||
11459             gameMode == MachinePlaysBlack) {
11460             StartClocks();
11461         } else {
11462             DisplayBothClocks();
11463         }
11464         if (gameMode == PlayFromGameFile) {
11465             if (appData.timeDelay >= 0) 
11466                 AutoPlayGameLoop();
11467         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11468             Reset(FALSE, TRUE);
11469             SendToICS(ics_prefix);
11470             SendToICS("refresh\n");
11471         } else if (currentMove < forwardMostMove) {
11472             ForwardInner(forwardMostMove);
11473         }
11474         pauseExamInvalid = FALSE;
11475     } else {
11476         switch (gameMode) {
11477           default:
11478             return;
11479           case IcsExamining:
11480             pauseExamForwardMostMove = forwardMostMove;
11481             pauseExamInvalid = FALSE;
11482             /* fall through */
11483           case IcsObserving:
11484           case IcsPlayingWhite:
11485           case IcsPlayingBlack:
11486             pausing = TRUE;
11487             ModeHighlight();
11488             return;
11489           case PlayFromGameFile:
11490             (void) StopLoadGameTimer();
11491             pausing = TRUE;
11492             ModeHighlight();
11493             break;
11494           case BeginningOfGame:
11495             if (appData.icsActive) return;
11496             /* else fall through */
11497           case MachinePlaysWhite:
11498           case MachinePlaysBlack:
11499           case TwoMachinesPlay:
11500             if (forwardMostMove == 0)
11501               return;           /* don't pause if no one has moved */
11502             if ((gameMode == MachinePlaysWhite &&
11503                  !WhiteOnMove(forwardMostMove)) ||
11504                 (gameMode == MachinePlaysBlack &&
11505                  WhiteOnMove(forwardMostMove))) {
11506                 StopClocks();
11507             }
11508             pausing = TRUE;
11509             ModeHighlight();
11510             break;
11511         }
11512     }
11513 }
11514
11515 void
11516 EditCommentEvent()
11517 {
11518     char title[MSG_SIZ];
11519
11520     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11521         strcpy(title, _("Edit comment"));
11522     } else {
11523         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11524                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11525                 parseList[currentMove - 1]);
11526     }
11527
11528     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11529 }
11530
11531
11532 void
11533 EditTagsEvent()
11534 {
11535     char *tags = PGNTags(&gameInfo);
11536     EditTagsPopUp(tags);
11537     free(tags);
11538 }
11539
11540 void
11541 AnalyzeModeEvent()
11542 {
11543     if (appData.noChessProgram || gameMode == AnalyzeMode)
11544       return;
11545
11546     if (gameMode != AnalyzeFile) {
11547         if (!appData.icsEngineAnalyze) {
11548                EditGameEvent();
11549                if (gameMode != EditGame) return;
11550         }
11551         ResurrectChessProgram();
11552         SendToProgram("analyze\n", &first);
11553         first.analyzing = TRUE;
11554         /*first.maybeThinking = TRUE;*/
11555         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11556         EngineOutputPopUp();
11557     }
11558     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11559     pausing = FALSE;
11560     ModeHighlight();
11561     SetGameInfo();
11562
11563     StartAnalysisClock();
11564     GetTimeMark(&lastNodeCountTime);
11565     lastNodeCount = 0;
11566 }
11567
11568 void
11569 AnalyzeFileEvent()
11570 {
11571     if (appData.noChessProgram || gameMode == AnalyzeFile)
11572       return;
11573
11574     if (gameMode != AnalyzeMode) {
11575         EditGameEvent();
11576         if (gameMode != EditGame) return;
11577         ResurrectChessProgram();
11578         SendToProgram("analyze\n", &first);
11579         first.analyzing = TRUE;
11580         /*first.maybeThinking = TRUE;*/
11581         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11582         EngineOutputPopUp();
11583     }
11584     gameMode = AnalyzeFile;
11585     pausing = FALSE;
11586     ModeHighlight();
11587     SetGameInfo();
11588
11589     StartAnalysisClock();
11590     GetTimeMark(&lastNodeCountTime);
11591     lastNodeCount = 0;
11592 }
11593
11594 void
11595 MachineWhiteEvent()
11596 {
11597     char buf[MSG_SIZ];
11598     char *bookHit = NULL;
11599
11600     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11601       return;
11602
11603
11604     if (gameMode == PlayFromGameFile || 
11605         gameMode == TwoMachinesPlay  || 
11606         gameMode == Training         || 
11607         gameMode == AnalyzeMode      || 
11608         gameMode == EndOfGame)
11609         EditGameEvent();
11610
11611     if (gameMode == EditPosition) 
11612         EditPositionDone(TRUE);
11613
11614     if (!WhiteOnMove(currentMove)) {
11615         DisplayError(_("It is not White's turn"), 0);
11616         return;
11617     }
11618   
11619     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11620       ExitAnalyzeMode();
11621
11622     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11623         gameMode == AnalyzeFile)
11624         TruncateGame();
11625
11626     ResurrectChessProgram();    /* in case it isn't running */
11627     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11628         gameMode = MachinePlaysWhite;
11629         ResetClocks();
11630     } else
11631     gameMode = MachinePlaysWhite;
11632     pausing = FALSE;
11633     ModeHighlight();
11634     SetGameInfo();
11635     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11636     DisplayTitle(buf);
11637     if (first.sendName) {
11638       sprintf(buf, "name %s\n", gameInfo.black);
11639       SendToProgram(buf, &first);
11640     }
11641     if (first.sendTime) {
11642       if (first.useColors) {
11643         SendToProgram("black\n", &first); /*gnu kludge*/
11644       }
11645       SendTimeRemaining(&first, TRUE);
11646     }
11647     if (first.useColors) {
11648       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11649     }
11650     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11651     SetMachineThinkingEnables();
11652     first.maybeThinking = TRUE;
11653     StartClocks();
11654     firstMove = FALSE;
11655
11656     if (appData.autoFlipView && !flipView) {
11657       flipView = !flipView;
11658       DrawPosition(FALSE, NULL);
11659       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11660     }
11661
11662     if(bookHit) { // [HGM] book: simulate book reply
11663         static char bookMove[MSG_SIZ]; // a bit generous?
11664
11665         programStats.nodes = programStats.depth = programStats.time = 
11666         programStats.score = programStats.got_only_move = 0;
11667         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11668
11669         strcpy(bookMove, "move ");
11670         strcat(bookMove, bookHit);
11671         HandleMachineMove(bookMove, &first);
11672     }
11673 }
11674
11675 void
11676 MachineBlackEvent()
11677 {
11678     char buf[MSG_SIZ];
11679    char *bookHit = NULL;
11680
11681     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11682         return;
11683
11684
11685     if (gameMode == PlayFromGameFile || 
11686         gameMode == TwoMachinesPlay  || 
11687         gameMode == Training         || 
11688         gameMode == AnalyzeMode      || 
11689         gameMode == EndOfGame)
11690         EditGameEvent();
11691
11692     if (gameMode == EditPosition) 
11693         EditPositionDone(TRUE);
11694
11695     if (WhiteOnMove(currentMove)) {
11696         DisplayError(_("It is not Black's turn"), 0);
11697         return;
11698     }
11699     
11700     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11701       ExitAnalyzeMode();
11702
11703     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11704         gameMode == AnalyzeFile)
11705         TruncateGame();
11706
11707     ResurrectChessProgram();    /* in case it isn't running */
11708     gameMode = MachinePlaysBlack;
11709     pausing = FALSE;
11710     ModeHighlight();
11711     SetGameInfo();
11712     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11713     DisplayTitle(buf);
11714     if (first.sendName) {
11715       sprintf(buf, "name %s\n", gameInfo.white);
11716       SendToProgram(buf, &first);
11717     }
11718     if (first.sendTime) {
11719       if (first.useColors) {
11720         SendToProgram("white\n", &first); /*gnu kludge*/
11721       }
11722       SendTimeRemaining(&first, FALSE);
11723     }
11724     if (first.useColors) {
11725       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11726     }
11727     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11728     SetMachineThinkingEnables();
11729     first.maybeThinking = TRUE;
11730     StartClocks();
11731
11732     if (appData.autoFlipView && flipView) {
11733       flipView = !flipView;
11734       DrawPosition(FALSE, NULL);
11735       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11736     }
11737     if(bookHit) { // [HGM] book: simulate book reply
11738         static char bookMove[MSG_SIZ]; // a bit generous?
11739
11740         programStats.nodes = programStats.depth = programStats.time = 
11741         programStats.score = programStats.got_only_move = 0;
11742         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11743
11744         strcpy(bookMove, "move ");
11745         strcat(bookMove, bookHit);
11746         HandleMachineMove(bookMove, &first);
11747     }
11748 }
11749
11750
11751 void
11752 DisplayTwoMachinesTitle()
11753 {
11754     char buf[MSG_SIZ];
11755     if (appData.matchGames > 0) {
11756         if (first.twoMachinesColor[0] == 'w') {
11757             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11758                     gameInfo.white, gameInfo.black,
11759                     first.matchWins, second.matchWins,
11760                     matchGame - 1 - (first.matchWins + second.matchWins));
11761         } else {
11762             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11763                     gameInfo.white, gameInfo.black,
11764                     second.matchWins, first.matchWins,
11765                     matchGame - 1 - (first.matchWins + second.matchWins));
11766         }
11767     } else {
11768         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11769     }
11770     DisplayTitle(buf);
11771 }
11772
11773 void
11774 TwoMachinesEvent P((void))
11775 {
11776     int i;
11777     char buf[MSG_SIZ];
11778     ChessProgramState *onmove;
11779     char *bookHit = NULL;
11780     
11781     if (appData.noChessProgram) return;
11782
11783     switch (gameMode) {
11784       case TwoMachinesPlay:
11785         return;
11786       case MachinePlaysWhite:
11787       case MachinePlaysBlack:
11788         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11789             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11790             return;
11791         }
11792         /* fall through */
11793       case BeginningOfGame:
11794       case PlayFromGameFile:
11795       case EndOfGame:
11796         EditGameEvent();
11797         if (gameMode != EditGame) return;
11798         break;
11799       case EditPosition:
11800         EditPositionDone(TRUE);
11801         break;
11802       case AnalyzeMode:
11803       case AnalyzeFile:
11804         ExitAnalyzeMode();
11805         break;
11806       case EditGame:
11807       default:
11808         break;
11809     }
11810
11811 //    forwardMostMove = currentMove;
11812     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11813     ResurrectChessProgram();    /* in case first program isn't running */
11814
11815     if (second.pr == NULL) {
11816         StartChessProgram(&second);
11817         if (second.protocolVersion == 1) {
11818           TwoMachinesEventIfReady();
11819         } else {
11820           /* kludge: allow timeout for initial "feature" command */
11821           FreezeUI();
11822           DisplayMessage("", _("Starting second chess program"));
11823           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11824         }
11825         return;
11826     }
11827     DisplayMessage("", "");
11828     InitChessProgram(&second, FALSE);
11829     SendToProgram("force\n", &second);
11830     if (startedFromSetupPosition) {
11831         SendBoard(&second, backwardMostMove);
11832     if (appData.debugMode) {
11833         fprintf(debugFP, "Two Machines\n");
11834     }
11835     }
11836     for (i = backwardMostMove; i < forwardMostMove; i++) {
11837         SendMoveToProgram(i, &second);
11838     }
11839
11840     gameMode = TwoMachinesPlay;
11841     pausing = FALSE;
11842     ModeHighlight();
11843     SetGameInfo();
11844     DisplayTwoMachinesTitle();
11845     firstMove = TRUE;
11846     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11847         onmove = &first;
11848     } else {
11849         onmove = &second;
11850     }
11851
11852     SendToProgram(first.computerString, &first);
11853     if (first.sendName) {
11854       sprintf(buf, "name %s\n", second.tidy);
11855       SendToProgram(buf, &first);
11856     }
11857     SendToProgram(second.computerString, &second);
11858     if (second.sendName) {
11859       sprintf(buf, "name %s\n", first.tidy);
11860       SendToProgram(buf, &second);
11861     }
11862
11863     ResetClocks();
11864     if (!first.sendTime || !second.sendTime) {
11865         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11866         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11867     }
11868     if (onmove->sendTime) {
11869       if (onmove->useColors) {
11870         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11871       }
11872       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11873     }
11874     if (onmove->useColors) {
11875       SendToProgram(onmove->twoMachinesColor, onmove);
11876     }
11877     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11878 //    SendToProgram("go\n", onmove);
11879     onmove->maybeThinking = TRUE;
11880     SetMachineThinkingEnables();
11881
11882     StartClocks();
11883
11884     if(bookHit) { // [HGM] book: simulate book reply
11885         static char bookMove[MSG_SIZ]; // a bit generous?
11886
11887         programStats.nodes = programStats.depth = programStats.time = 
11888         programStats.score = programStats.got_only_move = 0;
11889         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11890
11891         strcpy(bookMove, "move ");
11892         strcat(bookMove, bookHit);
11893         savedMessage = bookMove; // args for deferred call
11894         savedState = onmove;
11895         ScheduleDelayedEvent(DeferredBookMove, 1);
11896     }
11897 }
11898
11899 void
11900 TrainingEvent()
11901 {
11902     if (gameMode == Training) {
11903       SetTrainingModeOff();
11904       gameMode = PlayFromGameFile;
11905       DisplayMessage("", _("Training mode off"));
11906     } else {
11907       gameMode = Training;
11908       animateTraining = appData.animate;
11909
11910       /* make sure we are not already at the end of the game */
11911       if (currentMove < forwardMostMove) {
11912         SetTrainingModeOn();
11913         DisplayMessage("", _("Training mode on"));
11914       } else {
11915         gameMode = PlayFromGameFile;
11916         DisplayError(_("Already at end of game"), 0);
11917       }
11918     }
11919     ModeHighlight();
11920 }
11921
11922 void
11923 IcsClientEvent()
11924 {
11925     if (!appData.icsActive) return;
11926     switch (gameMode) {
11927       case IcsPlayingWhite:
11928       case IcsPlayingBlack:
11929       case IcsObserving:
11930       case IcsIdle:
11931       case BeginningOfGame:
11932       case IcsExamining:
11933         return;
11934
11935       case EditGame:
11936         break;
11937
11938       case EditPosition:
11939         EditPositionDone(TRUE);
11940         break;
11941
11942       case AnalyzeMode:
11943       case AnalyzeFile:
11944         ExitAnalyzeMode();
11945         break;
11946         
11947       default:
11948         EditGameEvent();
11949         break;
11950     }
11951
11952     gameMode = IcsIdle;
11953     ModeHighlight();
11954     return;
11955 }
11956
11957
11958 void
11959 EditGameEvent()
11960 {
11961     int i;
11962
11963     switch (gameMode) {
11964       case Training:
11965         SetTrainingModeOff();
11966         break;
11967       case MachinePlaysWhite:
11968       case MachinePlaysBlack:
11969       case BeginningOfGame:
11970         SendToProgram("force\n", &first);
11971         SetUserThinkingEnables();
11972         break;
11973       case PlayFromGameFile:
11974         (void) StopLoadGameTimer();
11975         if (gameFileFP != NULL) {
11976             gameFileFP = NULL;
11977         }
11978         break;
11979       case EditPosition:
11980         EditPositionDone(TRUE);
11981         break;
11982       case AnalyzeMode:
11983       case AnalyzeFile:
11984         ExitAnalyzeMode();
11985         SendToProgram("force\n", &first);
11986         break;
11987       case TwoMachinesPlay:
11988         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11989         ResurrectChessProgram();
11990         SetUserThinkingEnables();
11991         break;
11992       case EndOfGame:
11993         ResurrectChessProgram();
11994         break;
11995       case IcsPlayingBlack:
11996       case IcsPlayingWhite:
11997         DisplayError(_("Warning: You are still playing a game"), 0);
11998         break;
11999       case IcsObserving:
12000         DisplayError(_("Warning: You are still observing a game"), 0);
12001         break;
12002       case IcsExamining:
12003         DisplayError(_("Warning: You are still examining a game"), 0);
12004         break;
12005       case IcsIdle:
12006         break;
12007       case EditGame:
12008       default:
12009         return;
12010     }
12011     
12012     pausing = FALSE;
12013     StopClocks();
12014     first.offeredDraw = second.offeredDraw = 0;
12015
12016     if (gameMode == PlayFromGameFile) {
12017         whiteTimeRemaining = timeRemaining[0][currentMove];
12018         blackTimeRemaining = timeRemaining[1][currentMove];
12019         DisplayTitle("");
12020     }
12021
12022     if (gameMode == MachinePlaysWhite ||
12023         gameMode == MachinePlaysBlack ||
12024         gameMode == TwoMachinesPlay ||
12025         gameMode == EndOfGame) {
12026         i = forwardMostMove;
12027         while (i > currentMove) {
12028             SendToProgram("undo\n", &first);
12029             i--;
12030         }
12031         whiteTimeRemaining = timeRemaining[0][currentMove];
12032         blackTimeRemaining = timeRemaining[1][currentMove];
12033         DisplayBothClocks();
12034         if (whiteFlag || blackFlag) {
12035             whiteFlag = blackFlag = 0;
12036         }
12037         DisplayTitle("");
12038     }           
12039     
12040     gameMode = EditGame;
12041     ModeHighlight();
12042     SetGameInfo();
12043 }
12044
12045
12046 void
12047 EditPositionEvent()
12048 {
12049     if (gameMode == EditPosition) {
12050         EditGameEvent();
12051         return;
12052     }
12053     
12054     EditGameEvent();
12055     if (gameMode != EditGame) return;
12056     
12057     gameMode = EditPosition;
12058     ModeHighlight();
12059     SetGameInfo();
12060     if (currentMove > 0)
12061       CopyBoard(boards[0], boards[currentMove]);
12062     
12063     blackPlaysFirst = !WhiteOnMove(currentMove);
12064     ResetClocks();
12065     currentMove = forwardMostMove = backwardMostMove = 0;
12066     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12067     DisplayMove(-1);
12068 }
12069
12070 void
12071 ExitAnalyzeMode()
12072 {
12073     /* [DM] icsEngineAnalyze - possible call from other functions */
12074     if (appData.icsEngineAnalyze) {
12075         appData.icsEngineAnalyze = FALSE;
12076
12077         DisplayMessage("",_("Close ICS engine analyze..."));
12078     }
12079     if (first.analysisSupport && first.analyzing) {
12080       SendToProgram("exit\n", &first);
12081       first.analyzing = FALSE;
12082     }
12083     thinkOutput[0] = NULLCHAR;
12084 }
12085
12086 void
12087 EditPositionDone(Boolean fakeRights)
12088 {
12089     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12090
12091     startedFromSetupPosition = TRUE;
12092     InitChessProgram(&first, FALSE);
12093     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12094       boards[0][EP_STATUS] = EP_NONE;
12095       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12096     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12097         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12098         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12099       } else boards[0][CASTLING][2] = NoRights;
12100     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12101         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12102         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12103       } else boards[0][CASTLING][5] = NoRights;
12104     }
12105     SendToProgram("force\n", &first);
12106     if (blackPlaysFirst) {
12107         strcpy(moveList[0], "");
12108         strcpy(parseList[0], "");
12109         currentMove = forwardMostMove = backwardMostMove = 1;
12110         CopyBoard(boards[1], boards[0]);
12111     } else {
12112         currentMove = forwardMostMove = backwardMostMove = 0;
12113     }
12114     SendBoard(&first, forwardMostMove);
12115     if (appData.debugMode) {
12116         fprintf(debugFP, "EditPosDone\n");
12117     }
12118     DisplayTitle("");
12119     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12120     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12121     gameMode = EditGame;
12122     ModeHighlight();
12123     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12124     ClearHighlights(); /* [AS] */
12125 }
12126
12127 /* Pause for `ms' milliseconds */
12128 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12129 void
12130 TimeDelay(ms)
12131      long ms;
12132 {
12133     TimeMark m1, m2;
12134
12135     GetTimeMark(&m1);
12136     do {
12137         GetTimeMark(&m2);
12138     } while (SubtractTimeMarks(&m2, &m1) < ms);
12139 }
12140
12141 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12142 void
12143 SendMultiLineToICS(buf)
12144      char *buf;
12145 {
12146     char temp[MSG_SIZ+1], *p;
12147     int len;
12148
12149     len = strlen(buf);
12150     if (len > MSG_SIZ)
12151       len = MSG_SIZ;
12152   
12153     strncpy(temp, buf, len);
12154     temp[len] = 0;
12155
12156     p = temp;
12157     while (*p) {
12158         if (*p == '\n' || *p == '\r')
12159           *p = ' ';
12160         ++p;
12161     }
12162
12163     strcat(temp, "\n");
12164     SendToICS(temp);
12165     SendToPlayer(temp, strlen(temp));
12166 }
12167
12168 void
12169 SetWhiteToPlayEvent()
12170 {
12171     if (gameMode == EditPosition) {
12172         blackPlaysFirst = FALSE;
12173         DisplayBothClocks();    /* works because currentMove is 0 */
12174     } else if (gameMode == IcsExamining) {
12175         SendToICS(ics_prefix);
12176         SendToICS("tomove white\n");
12177     }
12178 }
12179
12180 void
12181 SetBlackToPlayEvent()
12182 {
12183     if (gameMode == EditPosition) {
12184         blackPlaysFirst = TRUE;
12185         currentMove = 1;        /* kludge */
12186         DisplayBothClocks();
12187         currentMove = 0;
12188     } else if (gameMode == IcsExamining) {
12189         SendToICS(ics_prefix);
12190         SendToICS("tomove black\n");
12191     }
12192 }
12193
12194 void
12195 EditPositionMenuEvent(selection, x, y)
12196      ChessSquare selection;
12197      int x, y;
12198 {
12199     char buf[MSG_SIZ];
12200     ChessSquare piece = boards[0][y][x];
12201
12202     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12203
12204     switch (selection) {
12205       case ClearBoard:
12206         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12207             SendToICS(ics_prefix);
12208             SendToICS("bsetup clear\n");
12209         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12210             SendToICS(ics_prefix);
12211             SendToICS("clearboard\n");
12212         } else {
12213             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12214                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12215                 for (y = 0; y < BOARD_HEIGHT; y++) {
12216                     if (gameMode == IcsExamining) {
12217                         if (boards[currentMove][y][x] != EmptySquare) {
12218                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12219                                     AAA + x, ONE + y);
12220                             SendToICS(buf);
12221                         }
12222                     } else {
12223                         boards[0][y][x] = p;
12224                     }
12225                 }
12226             }
12227         }
12228         if (gameMode == EditPosition) {
12229             DrawPosition(FALSE, boards[0]);
12230         }
12231         break;
12232
12233       case WhitePlay:
12234         SetWhiteToPlayEvent();
12235         break;
12236
12237       case BlackPlay:
12238         SetBlackToPlayEvent();
12239         break;
12240
12241       case EmptySquare:
12242         if (gameMode == IcsExamining) {
12243             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12244             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12245             SendToICS(buf);
12246         } else {
12247             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12248                 if(x == BOARD_LEFT-2) {
12249                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12250                     boards[0][y][1] = 0;
12251                 } else
12252                 if(x == BOARD_RGHT+1) {
12253                     if(y >= gameInfo.holdingsSize) break;
12254                     boards[0][y][BOARD_WIDTH-2] = 0;
12255                 } else break;
12256             }
12257             boards[0][y][x] = EmptySquare;
12258             DrawPosition(FALSE, boards[0]);
12259         }
12260         break;
12261
12262       case PromotePiece:
12263         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12264            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12265             selection = (ChessSquare) (PROMOTED piece);
12266         } else if(piece == EmptySquare) selection = WhiteSilver;
12267         else selection = (ChessSquare)((int)piece - 1);
12268         goto defaultlabel;
12269
12270       case DemotePiece:
12271         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12272            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12273             selection = (ChessSquare) (DEMOTED piece);
12274         } else if(piece == EmptySquare) selection = BlackSilver;
12275         else selection = (ChessSquare)((int)piece + 1);       
12276         goto defaultlabel;
12277
12278       case WhiteQueen:
12279       case BlackQueen:
12280         if(gameInfo.variant == VariantShatranj ||
12281            gameInfo.variant == VariantXiangqi  ||
12282            gameInfo.variant == VariantCourier  ||
12283            gameInfo.variant == VariantMakruk     )
12284             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12285         goto defaultlabel;
12286
12287       case WhiteKing:
12288       case BlackKing:
12289         if(gameInfo.variant == VariantXiangqi)
12290             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12291         if(gameInfo.variant == VariantKnightmate)
12292             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12293       default:
12294         defaultlabel:
12295         if (gameMode == IcsExamining) {
12296             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12297             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12298                     PieceToChar(selection), AAA + x, ONE + y);
12299             SendToICS(buf);
12300         } else {
12301             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12302                 int n;
12303                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12304                     n = PieceToNumber(selection - BlackPawn);
12305                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12306                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12307                     boards[0][BOARD_HEIGHT-1-n][1]++;
12308                 } else
12309                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12310                     n = PieceToNumber(selection);
12311                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12312                     boards[0][n][BOARD_WIDTH-1] = selection;
12313                     boards[0][n][BOARD_WIDTH-2]++;
12314                 }
12315             } else
12316             boards[0][y][x] = selection;
12317             DrawPosition(TRUE, boards[0]);
12318         }
12319         break;
12320     }
12321 }
12322
12323
12324 void
12325 DropMenuEvent(selection, x, y)
12326      ChessSquare selection;
12327      int x, y;
12328 {
12329     ChessMove moveType;
12330
12331     switch (gameMode) {
12332       case IcsPlayingWhite:
12333       case MachinePlaysBlack:
12334         if (!WhiteOnMove(currentMove)) {
12335             DisplayMoveError(_("It is Black's turn"));
12336             return;
12337         }
12338         moveType = WhiteDrop;
12339         break;
12340       case IcsPlayingBlack:
12341       case MachinePlaysWhite:
12342         if (WhiteOnMove(currentMove)) {
12343             DisplayMoveError(_("It is White's turn"));
12344             return;
12345         }
12346         moveType = BlackDrop;
12347         break;
12348       case EditGame:
12349         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12350         break;
12351       default:
12352         return;
12353     }
12354
12355     if (moveType == BlackDrop && selection < BlackPawn) {
12356       selection = (ChessSquare) ((int) selection
12357                                  + (int) BlackPawn - (int) WhitePawn);
12358     }
12359     if (boards[currentMove][y][x] != EmptySquare) {
12360         DisplayMoveError(_("That square is occupied"));
12361         return;
12362     }
12363
12364     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12365 }
12366
12367 void
12368 AcceptEvent()
12369 {
12370     /* Accept a pending offer of any kind from opponent */
12371     
12372     if (appData.icsActive) {
12373         SendToICS(ics_prefix);
12374         SendToICS("accept\n");
12375     } else if (cmailMsgLoaded) {
12376         if (currentMove == cmailOldMove &&
12377             commentList[cmailOldMove] != NULL &&
12378             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12379                    "Black offers a draw" : "White offers a draw")) {
12380             TruncateGame();
12381             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12382             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12383         } else {
12384             DisplayError(_("There is no pending offer on this move"), 0);
12385             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12386         }
12387     } else {
12388         /* Not used for offers from chess program */
12389     }
12390 }
12391
12392 void
12393 DeclineEvent()
12394 {
12395     /* Decline a pending offer of any kind from opponent */
12396     
12397     if (appData.icsActive) {
12398         SendToICS(ics_prefix);
12399         SendToICS("decline\n");
12400     } else if (cmailMsgLoaded) {
12401         if (currentMove == cmailOldMove &&
12402             commentList[cmailOldMove] != NULL &&
12403             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12404                    "Black offers a draw" : "White offers a draw")) {
12405 #ifdef NOTDEF
12406             AppendComment(cmailOldMove, "Draw declined", TRUE);
12407             DisplayComment(cmailOldMove - 1, "Draw declined");
12408 #endif /*NOTDEF*/
12409         } else {
12410             DisplayError(_("There is no pending offer on this move"), 0);
12411         }
12412     } else {
12413         /* Not used for offers from chess program */
12414     }
12415 }
12416
12417 void
12418 RematchEvent()
12419 {
12420     /* Issue ICS rematch command */
12421     if (appData.icsActive) {
12422         SendToICS(ics_prefix);
12423         SendToICS("rematch\n");
12424     }
12425 }
12426
12427 void
12428 CallFlagEvent()
12429 {
12430     /* Call your opponent's flag (claim a win on time) */
12431     if (appData.icsActive) {
12432         SendToICS(ics_prefix);
12433         SendToICS("flag\n");
12434     } else {
12435         switch (gameMode) {
12436           default:
12437             return;
12438           case MachinePlaysWhite:
12439             if (whiteFlag) {
12440                 if (blackFlag)
12441                   GameEnds(GameIsDrawn, "Both players ran out of time",
12442                            GE_PLAYER);
12443                 else
12444                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12445             } else {
12446                 DisplayError(_("Your opponent is not out of time"), 0);
12447             }
12448             break;
12449           case MachinePlaysBlack:
12450             if (blackFlag) {
12451                 if (whiteFlag)
12452                   GameEnds(GameIsDrawn, "Both players ran out of time",
12453                            GE_PLAYER);
12454                 else
12455                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12456             } else {
12457                 DisplayError(_("Your opponent is not out of time"), 0);
12458             }
12459             break;
12460         }
12461     }
12462 }
12463
12464 void
12465 DrawEvent()
12466 {
12467     /* Offer draw or accept pending draw offer from opponent */
12468     
12469     if (appData.icsActive) {
12470         /* Note: tournament rules require draw offers to be
12471            made after you make your move but before you punch
12472            your clock.  Currently ICS doesn't let you do that;
12473            instead, you immediately punch your clock after making
12474            a move, but you can offer a draw at any time. */
12475         
12476         SendToICS(ics_prefix);
12477         SendToICS("draw\n");
12478         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12479     } else if (cmailMsgLoaded) {
12480         if (currentMove == cmailOldMove &&
12481             commentList[cmailOldMove] != NULL &&
12482             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12483                    "Black offers a draw" : "White offers a draw")) {
12484             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12485             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12486         } else if (currentMove == cmailOldMove + 1) {
12487             char *offer = WhiteOnMove(cmailOldMove) ?
12488               "White offers a draw" : "Black offers a draw";
12489             AppendComment(currentMove, offer, TRUE);
12490             DisplayComment(currentMove - 1, offer);
12491             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12492         } else {
12493             DisplayError(_("You must make your move before offering a draw"), 0);
12494             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12495         }
12496     } else if (first.offeredDraw) {
12497         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12498     } else {
12499         if (first.sendDrawOffers) {
12500             SendToProgram("draw\n", &first);
12501             userOfferedDraw = TRUE;
12502         }
12503     }
12504 }
12505
12506 void
12507 AdjournEvent()
12508 {
12509     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12510     
12511     if (appData.icsActive) {
12512         SendToICS(ics_prefix);
12513         SendToICS("adjourn\n");
12514     } else {
12515         /* Currently GNU Chess doesn't offer or accept Adjourns */
12516     }
12517 }
12518
12519
12520 void
12521 AbortEvent()
12522 {
12523     /* Offer Abort or accept pending Abort offer from opponent */
12524     
12525     if (appData.icsActive) {
12526         SendToICS(ics_prefix);
12527         SendToICS("abort\n");
12528     } else {
12529         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12530     }
12531 }
12532
12533 void
12534 ResignEvent()
12535 {
12536     /* Resign.  You can do this even if it's not your turn. */
12537     
12538     if (appData.icsActive) {
12539         SendToICS(ics_prefix);
12540         SendToICS("resign\n");
12541     } else {
12542         switch (gameMode) {
12543           case MachinePlaysWhite:
12544             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12545             break;
12546           case MachinePlaysBlack:
12547             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12548             break;
12549           case EditGame:
12550             if (cmailMsgLoaded) {
12551                 TruncateGame();
12552                 if (WhiteOnMove(cmailOldMove)) {
12553                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12554                 } else {
12555                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12556                 }
12557                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12558             }
12559             break;
12560           default:
12561             break;
12562         }
12563     }
12564 }
12565
12566
12567 void
12568 StopObservingEvent()
12569 {
12570     /* Stop observing current games */
12571     SendToICS(ics_prefix);
12572     SendToICS("unobserve\n");
12573 }
12574
12575 void
12576 StopExaminingEvent()
12577 {
12578     /* Stop observing current game */
12579     SendToICS(ics_prefix);
12580     SendToICS("unexamine\n");
12581 }
12582
12583 void
12584 ForwardInner(target)
12585      int target;
12586 {
12587     int limit;
12588
12589     if (appData.debugMode)
12590         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12591                 target, currentMove, forwardMostMove);
12592
12593     if (gameMode == EditPosition)
12594       return;
12595
12596     if (gameMode == PlayFromGameFile && !pausing)
12597       PauseEvent();
12598     
12599     if (gameMode == IcsExamining && pausing)
12600       limit = pauseExamForwardMostMove;
12601     else
12602       limit = forwardMostMove;
12603     
12604     if (target > limit) target = limit;
12605
12606     if (target > 0 && moveList[target - 1][0]) {
12607         int fromX, fromY, toX, toY;
12608         toX = moveList[target - 1][2] - AAA;
12609         toY = moveList[target - 1][3] - ONE;
12610         if (moveList[target - 1][1] == '@') {
12611             if (appData.highlightLastMove) {
12612                 SetHighlights(-1, -1, toX, toY);
12613             }
12614         } else {
12615             fromX = moveList[target - 1][0] - AAA;
12616             fromY = moveList[target - 1][1] - ONE;
12617             if (target == currentMove + 1) {
12618                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12619             }
12620             if (appData.highlightLastMove) {
12621                 SetHighlights(fromX, fromY, toX, toY);
12622             }
12623         }
12624     }
12625     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12626         gameMode == Training || gameMode == PlayFromGameFile || 
12627         gameMode == AnalyzeFile) {
12628         while (currentMove < target) {
12629             SendMoveToProgram(currentMove++, &first);
12630         }
12631     } else {
12632         currentMove = target;
12633     }
12634     
12635     if (gameMode == EditGame || gameMode == EndOfGame) {
12636         whiteTimeRemaining = timeRemaining[0][currentMove];
12637         blackTimeRemaining = timeRemaining[1][currentMove];
12638     }
12639     DisplayBothClocks();
12640     DisplayMove(currentMove - 1);
12641     DrawPosition(FALSE, boards[currentMove]);
12642     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12643     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12644         DisplayComment(currentMove - 1, commentList[currentMove]);
12645     }
12646 }
12647
12648
12649 void
12650 ForwardEvent()
12651 {
12652     if (gameMode == IcsExamining && !pausing) {
12653         SendToICS(ics_prefix);
12654         SendToICS("forward\n");
12655     } else {
12656         ForwardInner(currentMove + 1);
12657     }
12658 }
12659
12660 void
12661 ToEndEvent()
12662 {
12663     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12664         /* to optimze, we temporarily turn off analysis mode while we feed
12665          * the remaining moves to the engine. Otherwise we get analysis output
12666          * after each move.
12667          */ 
12668         if (first.analysisSupport) {
12669           SendToProgram("exit\nforce\n", &first);
12670           first.analyzing = FALSE;
12671         }
12672     }
12673         
12674     if (gameMode == IcsExamining && !pausing) {
12675         SendToICS(ics_prefix);
12676         SendToICS("forward 999999\n");
12677     } else {
12678         ForwardInner(forwardMostMove);
12679     }
12680
12681     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12682         /* we have fed all the moves, so reactivate analysis mode */
12683         SendToProgram("analyze\n", &first);
12684         first.analyzing = TRUE;
12685         /*first.maybeThinking = TRUE;*/
12686         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12687     }
12688 }
12689
12690 void
12691 BackwardInner(target)
12692      int target;
12693 {
12694     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12695
12696     if (appData.debugMode)
12697         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12698                 target, currentMove, forwardMostMove);
12699
12700     if (gameMode == EditPosition) return;
12701     if (currentMove <= backwardMostMove) {
12702         ClearHighlights();
12703         DrawPosition(full_redraw, boards[currentMove]);
12704         return;
12705     }
12706     if (gameMode == PlayFromGameFile && !pausing)
12707       PauseEvent();
12708     
12709     if (moveList[target][0]) {
12710         int fromX, fromY, toX, toY;
12711         toX = moveList[target][2] - AAA;
12712         toY = moveList[target][3] - ONE;
12713         if (moveList[target][1] == '@') {
12714             if (appData.highlightLastMove) {
12715                 SetHighlights(-1, -1, toX, toY);
12716             }
12717         } else {
12718             fromX = moveList[target][0] - AAA;
12719             fromY = moveList[target][1] - ONE;
12720             if (target == currentMove - 1) {
12721                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12722             }
12723             if (appData.highlightLastMove) {
12724                 SetHighlights(fromX, fromY, toX, toY);
12725             }
12726         }
12727     }
12728     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12729         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12730         while (currentMove > target) {
12731             SendToProgram("undo\n", &first);
12732             currentMove--;
12733         }
12734     } else {
12735         currentMove = target;
12736     }
12737     
12738     if (gameMode == EditGame || gameMode == EndOfGame) {
12739         whiteTimeRemaining = timeRemaining[0][currentMove];
12740         blackTimeRemaining = timeRemaining[1][currentMove];
12741     }
12742     DisplayBothClocks();
12743     DisplayMove(currentMove - 1);
12744     DrawPosition(full_redraw, boards[currentMove]);
12745     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12746     // [HGM] PV info: routine tests if comment empty
12747     DisplayComment(currentMove - 1, commentList[currentMove]);
12748 }
12749
12750 void
12751 BackwardEvent()
12752 {
12753     if (gameMode == IcsExamining && !pausing) {
12754         SendToICS(ics_prefix);
12755         SendToICS("backward\n");
12756     } else {
12757         BackwardInner(currentMove - 1);
12758     }
12759 }
12760
12761 void
12762 ToStartEvent()
12763 {
12764     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12765         /* to optimize, we temporarily turn off analysis mode while we undo
12766          * all the moves. Otherwise we get analysis output after each undo.
12767          */ 
12768         if (first.analysisSupport) {
12769           SendToProgram("exit\nforce\n", &first);
12770           first.analyzing = FALSE;
12771         }
12772     }
12773
12774     if (gameMode == IcsExamining && !pausing) {
12775         SendToICS(ics_prefix);
12776         SendToICS("backward 999999\n");
12777     } else {
12778         BackwardInner(backwardMostMove);
12779     }
12780
12781     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12782         /* we have fed all the moves, so reactivate analysis mode */
12783         SendToProgram("analyze\n", &first);
12784         first.analyzing = TRUE;
12785         /*first.maybeThinking = TRUE;*/
12786         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12787     }
12788 }
12789
12790 void
12791 ToNrEvent(int to)
12792 {
12793   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12794   if (to >= forwardMostMove) to = forwardMostMove;
12795   if (to <= backwardMostMove) to = backwardMostMove;
12796   if (to < currentMove) {
12797     BackwardInner(to);
12798   } else {
12799     ForwardInner(to);
12800   }
12801 }
12802
12803 void
12804 RevertEvent(Boolean annotate)
12805 {
12806     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12807         return;
12808     }
12809     if (gameMode != IcsExamining) {
12810         DisplayError(_("You are not examining a game"), 0);
12811         return;
12812     }
12813     if (pausing) {
12814         DisplayError(_("You can't revert while pausing"), 0);
12815         return;
12816     }
12817     SendToICS(ics_prefix);
12818     SendToICS("revert\n");
12819 }
12820
12821 void
12822 RetractMoveEvent()
12823 {
12824     switch (gameMode) {
12825       case MachinePlaysWhite:
12826       case MachinePlaysBlack:
12827         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12828             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12829             return;
12830         }
12831         if (forwardMostMove < 2) return;
12832         currentMove = forwardMostMove = forwardMostMove - 2;
12833         whiteTimeRemaining = timeRemaining[0][currentMove];
12834         blackTimeRemaining = timeRemaining[1][currentMove];
12835         DisplayBothClocks();
12836         DisplayMove(currentMove - 1);
12837         ClearHighlights();/*!! could figure this out*/
12838         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12839         SendToProgram("remove\n", &first);
12840         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12841         break;
12842
12843       case BeginningOfGame:
12844       default:
12845         break;
12846
12847       case IcsPlayingWhite:
12848       case IcsPlayingBlack:
12849         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12850             SendToICS(ics_prefix);
12851             SendToICS("takeback 2\n");
12852         } else {
12853             SendToICS(ics_prefix);
12854             SendToICS("takeback 1\n");
12855         }
12856         break;
12857     }
12858 }
12859
12860 void
12861 MoveNowEvent()
12862 {
12863     ChessProgramState *cps;
12864
12865     switch (gameMode) {
12866       case MachinePlaysWhite:
12867         if (!WhiteOnMove(forwardMostMove)) {
12868             DisplayError(_("It is your turn"), 0);
12869             return;
12870         }
12871         cps = &first;
12872         break;
12873       case MachinePlaysBlack:
12874         if (WhiteOnMove(forwardMostMove)) {
12875             DisplayError(_("It is your turn"), 0);
12876             return;
12877         }
12878         cps = &first;
12879         break;
12880       case TwoMachinesPlay:
12881         if (WhiteOnMove(forwardMostMove) ==
12882             (first.twoMachinesColor[0] == 'w')) {
12883             cps = &first;
12884         } else {
12885             cps = &second;
12886         }
12887         break;
12888       case BeginningOfGame:
12889       default:
12890         return;
12891     }
12892     SendToProgram("?\n", cps);
12893 }
12894
12895 void
12896 TruncateGameEvent()
12897 {
12898     EditGameEvent();
12899     if (gameMode != EditGame) return;
12900     TruncateGame();
12901 }
12902
12903 void
12904 TruncateGame()
12905 {
12906     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12907     if (forwardMostMove > currentMove) {
12908         if (gameInfo.resultDetails != NULL) {
12909             free(gameInfo.resultDetails);
12910             gameInfo.resultDetails = NULL;
12911             gameInfo.result = GameUnfinished;
12912         }
12913         forwardMostMove = currentMove;
12914         HistorySet(parseList, backwardMostMove, forwardMostMove,
12915                    currentMove-1);
12916     }
12917 }
12918
12919 void
12920 HintEvent()
12921 {
12922     if (appData.noChessProgram) return;
12923     switch (gameMode) {
12924       case MachinePlaysWhite:
12925         if (WhiteOnMove(forwardMostMove)) {
12926             DisplayError(_("Wait until your turn"), 0);
12927             return;
12928         }
12929         break;
12930       case BeginningOfGame:
12931       case MachinePlaysBlack:
12932         if (!WhiteOnMove(forwardMostMove)) {
12933             DisplayError(_("Wait until your turn"), 0);
12934             return;
12935         }
12936         break;
12937       default:
12938         DisplayError(_("No hint available"), 0);
12939         return;
12940     }
12941     SendToProgram("hint\n", &first);
12942     hintRequested = TRUE;
12943 }
12944
12945 void
12946 BookEvent()
12947 {
12948     if (appData.noChessProgram) return;
12949     switch (gameMode) {
12950       case MachinePlaysWhite:
12951         if (WhiteOnMove(forwardMostMove)) {
12952             DisplayError(_("Wait until your turn"), 0);
12953             return;
12954         }
12955         break;
12956       case BeginningOfGame:
12957       case MachinePlaysBlack:
12958         if (!WhiteOnMove(forwardMostMove)) {
12959             DisplayError(_("Wait until your turn"), 0);
12960             return;
12961         }
12962         break;
12963       case EditPosition:
12964         EditPositionDone(TRUE);
12965         break;
12966       case TwoMachinesPlay:
12967         return;
12968       default:
12969         break;
12970     }
12971     SendToProgram("bk\n", &first);
12972     bookOutput[0] = NULLCHAR;
12973     bookRequested = TRUE;
12974 }
12975
12976 void
12977 AboutGameEvent()
12978 {
12979     char *tags = PGNTags(&gameInfo);
12980     TagsPopUp(tags, CmailMsg());
12981     free(tags);
12982 }
12983
12984 /* end button procedures */
12985
12986 void
12987 PrintPosition(fp, move)
12988      FILE *fp;
12989      int move;
12990 {
12991     int i, j;
12992     
12993     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12994         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12995             char c = PieceToChar(boards[move][i][j]);
12996             fputc(c == 'x' ? '.' : c, fp);
12997             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12998         }
12999     }
13000     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13001       fprintf(fp, "white to play\n");
13002     else
13003       fprintf(fp, "black to play\n");
13004 }
13005
13006 void
13007 PrintOpponents(fp)
13008      FILE *fp;
13009 {
13010     if (gameInfo.white != NULL) {
13011         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13012     } else {
13013         fprintf(fp, "\n");
13014     }
13015 }
13016
13017 /* Find last component of program's own name, using some heuristics */
13018 void
13019 TidyProgramName(prog, host, buf)
13020      char *prog, *host, buf[MSG_SIZ];
13021 {
13022     char *p, *q;
13023     int local = (strcmp(host, "localhost") == 0);
13024     while (!local && (p = strchr(prog, ';')) != NULL) {
13025         p++;
13026         while (*p == ' ') p++;
13027         prog = p;
13028     }
13029     if (*prog == '"' || *prog == '\'') {
13030         q = strchr(prog + 1, *prog);
13031     } else {
13032         q = strchr(prog, ' ');
13033     }
13034     if (q == NULL) q = prog + strlen(prog);
13035     p = q;
13036     while (p >= prog && *p != '/' && *p != '\\') p--;
13037     p++;
13038     if(p == prog && *p == '"') p++;
13039     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13040     memcpy(buf, p, q - p);
13041     buf[q - p] = NULLCHAR;
13042     if (!local) {
13043         strcat(buf, "@");
13044         strcat(buf, host);
13045     }
13046 }
13047
13048 char *
13049 TimeControlTagValue()
13050 {
13051     char buf[MSG_SIZ];
13052     if (!appData.clockMode) {
13053         strcpy(buf, "-");
13054     } else if (movesPerSession > 0) {
13055         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13056     } else if (timeIncrement == 0) {
13057         sprintf(buf, "%ld", timeControl/1000);
13058     } else {
13059         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13060     }
13061     return StrSave(buf);
13062 }
13063
13064 void
13065 SetGameInfo()
13066 {
13067     /* This routine is used only for certain modes */
13068     VariantClass v = gameInfo.variant;
13069     ChessMove r = GameUnfinished;
13070     char *p = NULL;
13071
13072     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13073         r = gameInfo.result; 
13074         p = gameInfo.resultDetails; 
13075         gameInfo.resultDetails = NULL;
13076     }
13077     ClearGameInfo(&gameInfo);
13078     gameInfo.variant = v;
13079
13080     switch (gameMode) {
13081       case MachinePlaysWhite:
13082         gameInfo.event = StrSave( appData.pgnEventHeader );
13083         gameInfo.site = StrSave(HostName());
13084         gameInfo.date = PGNDate();
13085         gameInfo.round = StrSave("-");
13086         gameInfo.white = StrSave(first.tidy);
13087         gameInfo.black = StrSave(UserName());
13088         gameInfo.timeControl = TimeControlTagValue();
13089         break;
13090
13091       case MachinePlaysBlack:
13092         gameInfo.event = StrSave( appData.pgnEventHeader );
13093         gameInfo.site = StrSave(HostName());
13094         gameInfo.date = PGNDate();
13095         gameInfo.round = StrSave("-");
13096         gameInfo.white = StrSave(UserName());
13097         gameInfo.black = StrSave(first.tidy);
13098         gameInfo.timeControl = TimeControlTagValue();
13099         break;
13100
13101       case TwoMachinesPlay:
13102         gameInfo.event = StrSave( appData.pgnEventHeader );
13103         gameInfo.site = StrSave(HostName());
13104         gameInfo.date = PGNDate();
13105         if (matchGame > 0) {
13106             char buf[MSG_SIZ];
13107             sprintf(buf, "%d", matchGame);
13108             gameInfo.round = StrSave(buf);
13109         } else {
13110             gameInfo.round = StrSave("-");
13111         }
13112         if (first.twoMachinesColor[0] == 'w') {
13113             gameInfo.white = StrSave(first.tidy);
13114             gameInfo.black = StrSave(second.tidy);
13115         } else {
13116             gameInfo.white = StrSave(second.tidy);
13117             gameInfo.black = StrSave(first.tidy);
13118         }
13119         gameInfo.timeControl = TimeControlTagValue();
13120         break;
13121
13122       case EditGame:
13123         gameInfo.event = StrSave("Edited game");
13124         gameInfo.site = StrSave(HostName());
13125         gameInfo.date = PGNDate();
13126         gameInfo.round = StrSave("-");
13127         gameInfo.white = StrSave("-");
13128         gameInfo.black = StrSave("-");
13129         gameInfo.result = r;
13130         gameInfo.resultDetails = p;
13131         break;
13132
13133       case EditPosition:
13134         gameInfo.event = StrSave("Edited position");
13135         gameInfo.site = StrSave(HostName());
13136         gameInfo.date = PGNDate();
13137         gameInfo.round = StrSave("-");
13138         gameInfo.white = StrSave("-");
13139         gameInfo.black = StrSave("-");
13140         break;
13141
13142       case IcsPlayingWhite:
13143       case IcsPlayingBlack:
13144       case IcsObserving:
13145       case IcsExamining:
13146         break;
13147
13148       case PlayFromGameFile:
13149         gameInfo.event = StrSave("Game from non-PGN file");
13150         gameInfo.site = StrSave(HostName());
13151         gameInfo.date = PGNDate();
13152         gameInfo.round = StrSave("-");
13153         gameInfo.white = StrSave("?");
13154         gameInfo.black = StrSave("?");
13155         break;
13156
13157       default:
13158         break;
13159     }
13160 }
13161
13162 void
13163 ReplaceComment(index, text)
13164      int index;
13165      char *text;
13166 {
13167     int len;
13168
13169     while (*text == '\n') text++;
13170     len = strlen(text);
13171     while (len > 0 && text[len - 1] == '\n') len--;
13172
13173     if (commentList[index] != NULL)
13174       free(commentList[index]);
13175
13176     if (len == 0) {
13177         commentList[index] = NULL;
13178         return;
13179     }
13180   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13181       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13182       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13183     commentList[index] = (char *) malloc(len + 2);
13184     strncpy(commentList[index], text, len);
13185     commentList[index][len] = '\n';
13186     commentList[index][len + 1] = NULLCHAR;
13187   } else { 
13188     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13189     char *p;
13190     commentList[index] = (char *) malloc(len + 6);
13191     strcpy(commentList[index], "{\n");
13192     strncpy(commentList[index]+2, text, len);
13193     commentList[index][len+2] = NULLCHAR;
13194     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13195     strcat(commentList[index], "\n}\n");
13196   }
13197 }
13198
13199 void
13200 CrushCRs(text)
13201      char *text;
13202 {
13203   char *p = text;
13204   char *q = text;
13205   char ch;
13206
13207   do {
13208     ch = *p++;
13209     if (ch == '\r') continue;
13210     *q++ = ch;
13211   } while (ch != '\0');
13212 }
13213
13214 void
13215 AppendComment(index, text, addBraces)
13216      int index;
13217      char *text;
13218      Boolean addBraces; // [HGM] braces: tells if we should add {}
13219 {
13220     int oldlen, len;
13221     char *old;
13222
13223 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13224     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13225
13226     CrushCRs(text);
13227     while (*text == '\n') text++;
13228     len = strlen(text);
13229     while (len > 0 && text[len - 1] == '\n') len--;
13230
13231     if (len == 0) return;
13232
13233     if (commentList[index] != NULL) {
13234         old = commentList[index];
13235         oldlen = strlen(old);
13236         while(commentList[index][oldlen-1] ==  '\n')
13237           commentList[index][--oldlen] = NULLCHAR;
13238         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13239         strcpy(commentList[index], old);
13240         free(old);
13241         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13242         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13243           if(addBraces) addBraces = FALSE; else { text++; len--; }
13244           while (*text == '\n') { text++; len--; }
13245           commentList[index][--oldlen] = NULLCHAR;
13246       }
13247         if(addBraces) strcat(commentList[index], "\n{\n");
13248         else          strcat(commentList[index], "\n");
13249         strcat(commentList[index], text);
13250         if(addBraces) strcat(commentList[index], "\n}\n");
13251         else          strcat(commentList[index], "\n");
13252     } else {
13253         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13254         if(addBraces)
13255              strcpy(commentList[index], "{\n");
13256         else commentList[index][0] = NULLCHAR;
13257         strcat(commentList[index], text);
13258         strcat(commentList[index], "\n");
13259         if(addBraces) strcat(commentList[index], "}\n");
13260     }
13261 }
13262
13263 static char * FindStr( char * text, char * sub_text )
13264 {
13265     char * result = strstr( text, sub_text );
13266
13267     if( result != NULL ) {
13268         result += strlen( sub_text );
13269     }
13270
13271     return result;
13272 }
13273
13274 /* [AS] Try to extract PV info from PGN comment */
13275 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13276 char *GetInfoFromComment( int index, char * text )
13277 {
13278     char * sep = text;
13279
13280     if( text != NULL && index > 0 ) {
13281         int score = 0;
13282         int depth = 0;
13283         int time = -1, sec = 0, deci;
13284         char * s_eval = FindStr( text, "[%eval " );
13285         char * s_emt = FindStr( text, "[%emt " );
13286
13287         if( s_eval != NULL || s_emt != NULL ) {
13288             /* New style */
13289             char delim;
13290
13291             if( s_eval != NULL ) {
13292                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13293                     return text;
13294                 }
13295
13296                 if( delim != ']' ) {
13297                     return text;
13298                 }
13299             }
13300
13301             if( s_emt != NULL ) {
13302             }
13303                 return text;
13304         }
13305         else {
13306             /* We expect something like: [+|-]nnn.nn/dd */
13307             int score_lo = 0;
13308
13309             if(*text != '{') return text; // [HGM] braces: must be normal comment
13310
13311             sep = strchr( text, '/' );
13312             if( sep == NULL || sep < (text+4) ) {
13313                 return text;
13314             }
13315
13316             time = -1; sec = -1; deci = -1;
13317             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13318                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13319                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13320                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13321                 return text;
13322             }
13323
13324             if( score_lo < 0 || score_lo >= 100 ) {
13325                 return text;
13326             }
13327
13328             if(sec >= 0) time = 600*time + 10*sec; else
13329             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13330
13331             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13332
13333             /* [HGM] PV time: now locate end of PV info */
13334             while( *++sep >= '0' && *sep <= '9'); // strip depth
13335             if(time >= 0)
13336             while( *++sep >= '0' && *sep <= '9'); // strip time
13337             if(sec >= 0)
13338             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13339             if(deci >= 0)
13340             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13341             while(*sep == ' ') sep++;
13342         }
13343
13344         if( depth <= 0 ) {
13345             return text;
13346         }
13347
13348         if( time < 0 ) {
13349             time = -1;
13350         }
13351
13352         pvInfoList[index-1].depth = depth;
13353         pvInfoList[index-1].score = score;
13354         pvInfoList[index-1].time  = 10*time; // centi-sec
13355         if(*sep == '}') *sep = 0; else *--sep = '{';
13356     }
13357     return sep;
13358 }
13359
13360 void
13361 SendToProgram(message, cps)
13362      char *message;
13363      ChessProgramState *cps;
13364 {
13365     int count, outCount, error;
13366     char buf[MSG_SIZ];
13367
13368     if (cps->pr == NULL) return;
13369     Attention(cps);
13370     
13371     if (appData.debugMode) {
13372         TimeMark now;
13373         GetTimeMark(&now);
13374         fprintf(debugFP, "%ld >%-6s: %s", 
13375                 SubtractTimeMarks(&now, &programStartTime),
13376                 cps->which, message);
13377     }
13378     
13379     count = strlen(message);
13380     outCount = OutputToProcess(cps->pr, message, count, &error);
13381     if (outCount < count && !exiting 
13382                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13383         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13384         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13385             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13386                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13387                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13388             } else {
13389                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13390             }
13391             gameInfo.resultDetails = StrSave(buf);
13392         }
13393         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13394     }
13395 }
13396
13397 void
13398 ReceiveFromProgram(isr, closure, message, count, error)
13399      InputSourceRef isr;
13400      VOIDSTAR closure;
13401      char *message;
13402      int count;
13403      int error;
13404 {
13405     char *end_str;
13406     char buf[MSG_SIZ];
13407     ChessProgramState *cps = (ChessProgramState *)closure;
13408
13409     if (isr != cps->isr) return; /* Killed intentionally */
13410     if (count <= 0) {
13411         if (count == 0) {
13412             sprintf(buf,
13413                     _("Error: %s chess program (%s) exited unexpectedly"),
13414                     cps->which, cps->program);
13415         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13416                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13417                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13418                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13419                 } else {
13420                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13421                 }
13422                 gameInfo.resultDetails = StrSave(buf);
13423             }
13424             RemoveInputSource(cps->isr);
13425             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13426         } else {
13427             sprintf(buf,
13428                     _("Error reading from %s chess program (%s)"),
13429                     cps->which, cps->program);
13430             RemoveInputSource(cps->isr);
13431
13432             /* [AS] Program is misbehaving badly... kill it */
13433             if( count == -2 ) {
13434                 DestroyChildProcess( cps->pr, 9 );
13435                 cps->pr = NoProc;
13436             }
13437
13438             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13439         }
13440         return;
13441     }
13442     
13443     if ((end_str = strchr(message, '\r')) != NULL)
13444       *end_str = NULLCHAR;
13445     if ((end_str = strchr(message, '\n')) != NULL)
13446       *end_str = NULLCHAR;
13447     
13448     if (appData.debugMode) {
13449         TimeMark now; int print = 1;
13450         char *quote = ""; char c; int i;
13451
13452         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13453                 char start = message[0];
13454                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13455                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13456                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13457                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13458                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13459                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13460                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13461                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13462                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13463                     print = (appData.engineComments >= 2);
13464                 }
13465                 message[0] = start; // restore original message
13466         }
13467         if(print) {
13468                 GetTimeMark(&now);
13469                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13470                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13471                         quote,
13472                         message);
13473         }
13474     }
13475
13476     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13477     if (appData.icsEngineAnalyze) {
13478         if (strstr(message, "whisper") != NULL ||
13479              strstr(message, "kibitz") != NULL || 
13480             strstr(message, "tellics") != NULL) return;
13481     }
13482
13483     HandleMachineMove(message, cps);
13484 }
13485
13486
13487 void
13488 SendTimeControl(cps, mps, tc, inc, sd, st)
13489      ChessProgramState *cps;
13490      int mps, inc, sd, st;
13491      long tc;
13492 {
13493     char buf[MSG_SIZ];
13494     int seconds;
13495
13496     if( timeControl_2 > 0 ) {
13497         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13498             tc = timeControl_2;
13499         }
13500     }
13501     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13502     inc /= cps->timeOdds;
13503     st  /= cps->timeOdds;
13504
13505     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13506
13507     if (st > 0) {
13508       /* Set exact time per move, normally using st command */
13509       if (cps->stKludge) {
13510         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13511         seconds = st % 60;
13512         if (seconds == 0) {
13513           sprintf(buf, "level 1 %d\n", st/60);
13514         } else {
13515           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13516         }
13517       } else {
13518         sprintf(buf, "st %d\n", st);
13519       }
13520     } else {
13521       /* Set conventional or incremental time control, using level command */
13522       if (seconds == 0) {
13523         /* Note old gnuchess bug -- minutes:seconds used to not work.
13524            Fixed in later versions, but still avoid :seconds
13525            when seconds is 0. */
13526         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13527       } else {
13528         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13529                 seconds, inc/1000);
13530       }
13531     }
13532     SendToProgram(buf, cps);
13533
13534     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13535     /* Orthogonally, limit search to given depth */
13536     if (sd > 0) {
13537       if (cps->sdKludge) {
13538         sprintf(buf, "depth\n%d\n", sd);
13539       } else {
13540         sprintf(buf, "sd %d\n", sd);
13541       }
13542       SendToProgram(buf, cps);
13543     }
13544
13545     if(cps->nps > 0) { /* [HGM] nps */
13546         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13547         else {
13548                 sprintf(buf, "nps %d\n", cps->nps);
13549               SendToProgram(buf, cps);
13550         }
13551     }
13552 }
13553
13554 ChessProgramState *WhitePlayer()
13555 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13556 {
13557     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13558        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13559         return &second;
13560     return &first;
13561 }
13562
13563 void
13564 SendTimeRemaining(cps, machineWhite)
13565      ChessProgramState *cps;
13566      int /*boolean*/ machineWhite;
13567 {
13568     char message[MSG_SIZ];
13569     long time, otime;
13570
13571     /* Note: this routine must be called when the clocks are stopped
13572        or when they have *just* been set or switched; otherwise
13573        it will be off by the time since the current tick started.
13574     */
13575     if (machineWhite) {
13576         time = whiteTimeRemaining / 10;
13577         otime = blackTimeRemaining / 10;
13578     } else {
13579         time = blackTimeRemaining / 10;
13580         otime = whiteTimeRemaining / 10;
13581     }
13582     /* [HGM] translate opponent's time by time-odds factor */
13583     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13584     if (appData.debugMode) {
13585         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13586     }
13587
13588     if (time <= 0) time = 1;
13589     if (otime <= 0) otime = 1;
13590     
13591     sprintf(message, "time %ld\n", time);
13592     SendToProgram(message, cps);
13593
13594     sprintf(message, "otim %ld\n", otime);
13595     SendToProgram(message, cps);
13596 }
13597
13598 int
13599 BoolFeature(p, name, loc, cps)
13600      char **p;
13601      char *name;
13602      int *loc;
13603      ChessProgramState *cps;
13604 {
13605   char buf[MSG_SIZ];
13606   int len = strlen(name);
13607   int val;
13608   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13609     (*p) += len + 1;
13610     sscanf(*p, "%d", &val);
13611     *loc = (val != 0);
13612     while (**p && **p != ' ') (*p)++;
13613     sprintf(buf, "accepted %s\n", name);
13614     SendToProgram(buf, cps);
13615     return TRUE;
13616   }
13617   return FALSE;
13618 }
13619
13620 int
13621 IntFeature(p, name, loc, cps)
13622      char **p;
13623      char *name;
13624      int *loc;
13625      ChessProgramState *cps;
13626 {
13627   char buf[MSG_SIZ];
13628   int len = strlen(name);
13629   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13630     (*p) += len + 1;
13631     sscanf(*p, "%d", loc);
13632     while (**p && **p != ' ') (*p)++;
13633     sprintf(buf, "accepted %s\n", name);
13634     SendToProgram(buf, cps);
13635     return TRUE;
13636   }
13637   return FALSE;
13638 }
13639
13640 int
13641 StringFeature(p, name, loc, cps)
13642      char **p;
13643      char *name;
13644      char loc[];
13645      ChessProgramState *cps;
13646 {
13647   char buf[MSG_SIZ];
13648   int len = strlen(name);
13649   if (strncmp((*p), name, len) == 0
13650       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13651     (*p) += len + 2;
13652     sscanf(*p, "%[^\"]", loc);
13653     while (**p && **p != '\"') (*p)++;
13654     if (**p == '\"') (*p)++;
13655     sprintf(buf, "accepted %s\n", name);
13656     SendToProgram(buf, cps);
13657     return TRUE;
13658   }
13659   return FALSE;
13660 }
13661
13662 int 
13663 ParseOption(Option *opt, ChessProgramState *cps)
13664 // [HGM] options: process the string that defines an engine option, and determine
13665 // name, type, default value, and allowed value range
13666 {
13667         char *p, *q, buf[MSG_SIZ];
13668         int n, min = (-1)<<31, max = 1<<31, def;
13669
13670         if(p = strstr(opt->name, " -spin ")) {
13671             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13672             if(max < min) max = min; // enforce consistency
13673             if(def < min) def = min;
13674             if(def > max) def = max;
13675             opt->value = def;
13676             opt->min = min;
13677             opt->max = max;
13678             opt->type = Spin;
13679         } else if((p = strstr(opt->name, " -slider "))) {
13680             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13681             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13682             if(max < min) max = min; // enforce consistency
13683             if(def < min) def = min;
13684             if(def > max) def = max;
13685             opt->value = def;
13686             opt->min = min;
13687             opt->max = max;
13688             opt->type = Spin; // Slider;
13689         } else if((p = strstr(opt->name, " -string "))) {
13690             opt->textValue = p+9;
13691             opt->type = TextBox;
13692         } else if((p = strstr(opt->name, " -file "))) {
13693             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13694             opt->textValue = p+7;
13695             opt->type = TextBox; // FileName;
13696         } else if((p = strstr(opt->name, " -path "))) {
13697             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13698             opt->textValue = p+7;
13699             opt->type = TextBox; // PathName;
13700         } else if(p = strstr(opt->name, " -check ")) {
13701             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13702             opt->value = (def != 0);
13703             opt->type = CheckBox;
13704         } else if(p = strstr(opt->name, " -combo ")) {
13705             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13706             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13707             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13708             opt->value = n = 0;
13709             while(q = StrStr(q, " /// ")) {
13710                 n++; *q = 0;    // count choices, and null-terminate each of them
13711                 q += 5;
13712                 if(*q == '*') { // remember default, which is marked with * prefix
13713                     q++;
13714                     opt->value = n;
13715                 }
13716                 cps->comboList[cps->comboCnt++] = q;
13717             }
13718             cps->comboList[cps->comboCnt++] = NULL;
13719             opt->max = n + 1;
13720             opt->type = ComboBox;
13721         } else if(p = strstr(opt->name, " -button")) {
13722             opt->type = Button;
13723         } else if(p = strstr(opt->name, " -save")) {
13724             opt->type = SaveButton;
13725         } else return FALSE;
13726         *p = 0; // terminate option name
13727         // now look if the command-line options define a setting for this engine option.
13728         if(cps->optionSettings && cps->optionSettings[0])
13729             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13730         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13731                 sprintf(buf, "option %s", p);
13732                 if(p = strstr(buf, ",")) *p = 0;
13733                 strcat(buf, "\n");
13734                 SendToProgram(buf, cps);
13735         }
13736         return TRUE;
13737 }
13738
13739 void
13740 FeatureDone(cps, val)
13741      ChessProgramState* cps;
13742      int val;
13743 {
13744   DelayedEventCallback cb = GetDelayedEvent();
13745   if ((cb == InitBackEnd3 && cps == &first) ||
13746       (cb == TwoMachinesEventIfReady && cps == &second)) {
13747     CancelDelayedEvent();
13748     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13749   }
13750   cps->initDone = val;
13751 }
13752
13753 /* Parse feature command from engine */
13754 void
13755 ParseFeatures(args, cps)
13756      char* args;
13757      ChessProgramState *cps;  
13758 {
13759   char *p = args;
13760   char *q;
13761   int val;
13762   char buf[MSG_SIZ];
13763
13764   for (;;) {
13765     while (*p == ' ') p++;
13766     if (*p == NULLCHAR) return;
13767
13768     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13769     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13770     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13771     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13772     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13773     if (BoolFeature(&p, "reuse", &val, cps)) {
13774       /* Engine can disable reuse, but can't enable it if user said no */
13775       if (!val) cps->reuse = FALSE;
13776       continue;
13777     }
13778     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13779     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13780       if (gameMode == TwoMachinesPlay) {
13781         DisplayTwoMachinesTitle();
13782       } else {
13783         DisplayTitle("");
13784       }
13785       continue;
13786     }
13787     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13788     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13789     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13790     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13791     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13792     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13793     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13794     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13795     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13796     if (IntFeature(&p, "done", &val, cps)) {
13797       FeatureDone(cps, val);
13798       continue;
13799     }
13800     /* Added by Tord: */
13801     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13802     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13803     /* End of additions by Tord */
13804
13805     /* [HGM] added features: */
13806     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13807     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13808     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13809     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13810     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13811     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13812     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13813         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13814             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13815             SendToProgram(buf, cps);
13816             continue;
13817         }
13818         if(cps->nrOptions >= MAX_OPTIONS) {
13819             cps->nrOptions--;
13820             sprintf(buf, "%s engine has too many options\n", cps->which);
13821             DisplayError(buf, 0);
13822         }
13823         continue;
13824     }
13825     /* End of additions by HGM */
13826
13827     /* unknown feature: complain and skip */
13828     q = p;
13829     while (*q && *q != '=') q++;
13830     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13831     SendToProgram(buf, cps);
13832     p = q;
13833     if (*p == '=') {
13834       p++;
13835       if (*p == '\"') {
13836         p++;
13837         while (*p && *p != '\"') p++;
13838         if (*p == '\"') p++;
13839       } else {
13840         while (*p && *p != ' ') p++;
13841       }
13842     }
13843   }
13844
13845 }
13846
13847 void
13848 PeriodicUpdatesEvent(newState)
13849      int newState;
13850 {
13851     if (newState == appData.periodicUpdates)
13852       return;
13853
13854     appData.periodicUpdates=newState;
13855
13856     /* Display type changes, so update it now */
13857 //    DisplayAnalysis();
13858
13859     /* Get the ball rolling again... */
13860     if (newState) {
13861         AnalysisPeriodicEvent(1);
13862         StartAnalysisClock();
13863     }
13864 }
13865
13866 void
13867 PonderNextMoveEvent(newState)
13868      int newState;
13869 {
13870     if (newState == appData.ponderNextMove) return;
13871     if (gameMode == EditPosition) EditPositionDone(TRUE);
13872     if (newState) {
13873         SendToProgram("hard\n", &first);
13874         if (gameMode == TwoMachinesPlay) {
13875             SendToProgram("hard\n", &second);
13876         }
13877     } else {
13878         SendToProgram("easy\n", &first);
13879         thinkOutput[0] = NULLCHAR;
13880         if (gameMode == TwoMachinesPlay) {
13881             SendToProgram("easy\n", &second);
13882         }
13883     }
13884     appData.ponderNextMove = newState;
13885 }
13886
13887 void
13888 NewSettingEvent(option, feature, command, value)
13889      char *command;
13890      int option, value, *feature;
13891 {
13892     char buf[MSG_SIZ];
13893
13894     if (gameMode == EditPosition) EditPositionDone(TRUE);
13895     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13896     if(feature == NULL || *feature) SendToProgram(buf, &first);
13897     if (gameMode == TwoMachinesPlay) {
13898         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13899     }
13900 }
13901
13902 void
13903 ShowThinkingEvent()
13904 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13905 {
13906     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13907     int newState = appData.showThinking
13908         // [HGM] thinking: other features now need thinking output as well
13909         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13910     
13911     if (oldState == newState) return;
13912     oldState = newState;
13913     if (gameMode == EditPosition) EditPositionDone(TRUE);
13914     if (oldState) {
13915         SendToProgram("post\n", &first);
13916         if (gameMode == TwoMachinesPlay) {
13917             SendToProgram("post\n", &second);
13918         }
13919     } else {
13920         SendToProgram("nopost\n", &first);
13921         thinkOutput[0] = NULLCHAR;
13922         if (gameMode == TwoMachinesPlay) {
13923             SendToProgram("nopost\n", &second);
13924         }
13925     }
13926 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13927 }
13928
13929 void
13930 AskQuestionEvent(title, question, replyPrefix, which)
13931      char *title; char *question; char *replyPrefix; char *which;
13932 {
13933   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13934   if (pr == NoProc) return;
13935   AskQuestion(title, question, replyPrefix, pr);
13936 }
13937
13938 void
13939 DisplayMove(moveNumber)
13940      int moveNumber;
13941 {
13942     char message[MSG_SIZ];
13943     char res[MSG_SIZ];
13944     char cpThinkOutput[MSG_SIZ];
13945
13946     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13947     
13948     if (moveNumber == forwardMostMove - 1 || 
13949         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13950
13951         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13952
13953         if (strchr(cpThinkOutput, '\n')) {
13954             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13955         }
13956     } else {
13957         *cpThinkOutput = NULLCHAR;
13958     }
13959
13960     /* [AS] Hide thinking from human user */
13961     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13962         *cpThinkOutput = NULLCHAR;
13963         if( thinkOutput[0] != NULLCHAR ) {
13964             int i;
13965
13966             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13967                 cpThinkOutput[i] = '.';
13968             }
13969             cpThinkOutput[i] = NULLCHAR;
13970             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13971         }
13972     }
13973
13974     if (moveNumber == forwardMostMove - 1 &&
13975         gameInfo.resultDetails != NULL) {
13976         if (gameInfo.resultDetails[0] == NULLCHAR) {
13977             sprintf(res, " %s", PGNResult(gameInfo.result));
13978         } else {
13979             sprintf(res, " {%s} %s",
13980                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13981         }
13982     } else {
13983         res[0] = NULLCHAR;
13984     }
13985
13986     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13987         DisplayMessage(res, cpThinkOutput);
13988     } else {
13989         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13990                 WhiteOnMove(moveNumber) ? " " : ".. ",
13991                 parseList[moveNumber], res);
13992         DisplayMessage(message, cpThinkOutput);
13993     }
13994 }
13995
13996 void
13997 DisplayComment(moveNumber, text)
13998      int moveNumber;
13999      char *text;
14000 {
14001     char title[MSG_SIZ];
14002     char buf[8000]; // comment can be long!
14003     int score, depth;
14004     
14005     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14006       strcpy(title, "Comment");
14007     } else {
14008       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14009               WhiteOnMove(moveNumber) ? " " : ".. ",
14010               parseList[moveNumber]);
14011     }
14012     // [HGM] PV info: display PV info together with (or as) comment
14013     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14014       if(text == NULL) text = "";                                           
14015       score = pvInfoList[moveNumber].score;
14016       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14017               depth, (pvInfoList[moveNumber].time+50)/100, text);
14018       text = buf;
14019     }
14020     if (text != NULL && (appData.autoDisplayComment || commentUp))
14021         CommentPopUp(title, text);
14022 }
14023
14024 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14025  * might be busy thinking or pondering.  It can be omitted if your
14026  * gnuchess is configured to stop thinking immediately on any user
14027  * input.  However, that gnuchess feature depends on the FIONREAD
14028  * ioctl, which does not work properly on some flavors of Unix.
14029  */
14030 void
14031 Attention(cps)
14032      ChessProgramState *cps;
14033 {
14034 #if ATTENTION
14035     if (!cps->useSigint) return;
14036     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14037     switch (gameMode) {
14038       case MachinePlaysWhite:
14039       case MachinePlaysBlack:
14040       case TwoMachinesPlay:
14041       case IcsPlayingWhite:
14042       case IcsPlayingBlack:
14043       case AnalyzeMode:
14044       case AnalyzeFile:
14045         /* Skip if we know it isn't thinking */
14046         if (!cps->maybeThinking) return;
14047         if (appData.debugMode)
14048           fprintf(debugFP, "Interrupting %s\n", cps->which);
14049         InterruptChildProcess(cps->pr);
14050         cps->maybeThinking = FALSE;
14051         break;
14052       default:
14053         break;
14054     }
14055 #endif /*ATTENTION*/
14056 }
14057
14058 int
14059 CheckFlags()
14060 {
14061     if (whiteTimeRemaining <= 0) {
14062         if (!whiteFlag) {
14063             whiteFlag = TRUE;
14064             if (appData.icsActive) {
14065                 if (appData.autoCallFlag &&
14066                     gameMode == IcsPlayingBlack && !blackFlag) {
14067                   SendToICS(ics_prefix);
14068                   SendToICS("flag\n");
14069                 }
14070             } else {
14071                 if (blackFlag) {
14072                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14073                 } else {
14074                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14075                     if (appData.autoCallFlag) {
14076                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14077                         return TRUE;
14078                     }
14079                 }
14080             }
14081         }
14082     }
14083     if (blackTimeRemaining <= 0) {
14084         if (!blackFlag) {
14085             blackFlag = TRUE;
14086             if (appData.icsActive) {
14087                 if (appData.autoCallFlag &&
14088                     gameMode == IcsPlayingWhite && !whiteFlag) {
14089                   SendToICS(ics_prefix);
14090                   SendToICS("flag\n");
14091                 }
14092             } else {
14093                 if (whiteFlag) {
14094                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14095                 } else {
14096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14097                     if (appData.autoCallFlag) {
14098                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14099                         return TRUE;
14100                     }
14101                 }
14102             }
14103         }
14104     }
14105     return FALSE;
14106 }
14107
14108 void
14109 CheckTimeControl()
14110 {
14111     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14112         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14113
14114     /*
14115      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14116      */
14117     if ( !WhiteOnMove(forwardMostMove) )
14118         /* White made time control */
14119         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14120         /* [HGM] time odds: correct new time quota for time odds! */
14121                                             / WhitePlayer()->timeOdds;
14122       else
14123         /* Black made time control */
14124         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14125                                             / WhitePlayer()->other->timeOdds;
14126 }
14127
14128 void
14129 DisplayBothClocks()
14130 {
14131     int wom = gameMode == EditPosition ?
14132       !blackPlaysFirst : WhiteOnMove(currentMove);
14133     DisplayWhiteClock(whiteTimeRemaining, wom);
14134     DisplayBlackClock(blackTimeRemaining, !wom);
14135 }
14136
14137
14138 /* Timekeeping seems to be a portability nightmare.  I think everyone
14139    has ftime(), but I'm really not sure, so I'm including some ifdefs
14140    to use other calls if you don't.  Clocks will be less accurate if
14141    you have neither ftime nor gettimeofday.
14142 */
14143
14144 /* VS 2008 requires the #include outside of the function */
14145 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14146 #include <sys/timeb.h>
14147 #endif
14148
14149 /* Get the current time as a TimeMark */
14150 void
14151 GetTimeMark(tm)
14152      TimeMark *tm;
14153 {
14154 #if HAVE_GETTIMEOFDAY
14155
14156     struct timeval timeVal;
14157     struct timezone timeZone;
14158
14159     gettimeofday(&timeVal, &timeZone);
14160     tm->sec = (long) timeVal.tv_sec; 
14161     tm->ms = (int) (timeVal.tv_usec / 1000L);
14162
14163 #else /*!HAVE_GETTIMEOFDAY*/
14164 #if HAVE_FTIME
14165
14166 // include <sys/timeb.h> / moved to just above start of function
14167     struct timeb timeB;
14168
14169     ftime(&timeB);
14170     tm->sec = (long) timeB.time;
14171     tm->ms = (int) timeB.millitm;
14172
14173 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14174     tm->sec = (long) time(NULL);
14175     tm->ms = 0;
14176 #endif
14177 #endif
14178 }
14179
14180 /* Return the difference in milliseconds between two
14181    time marks.  We assume the difference will fit in a long!
14182 */
14183 long
14184 SubtractTimeMarks(tm2, tm1)
14185      TimeMark *tm2, *tm1;
14186 {
14187     return 1000L*(tm2->sec - tm1->sec) +
14188            (long) (tm2->ms - tm1->ms);
14189 }
14190
14191
14192 /*
14193  * Code to manage the game clocks.
14194  *
14195  * In tournament play, black starts the clock and then white makes a move.
14196  * We give the human user a slight advantage if he is playing white---the
14197  * clocks don't run until he makes his first move, so it takes zero time.
14198  * Also, we don't account for network lag, so we could get out of sync
14199  * with GNU Chess's clock -- but then, referees are always right.  
14200  */
14201
14202 static TimeMark tickStartTM;
14203 static long intendedTickLength;
14204
14205 long
14206 NextTickLength(timeRemaining)
14207      long timeRemaining;
14208 {
14209     long nominalTickLength, nextTickLength;
14210
14211     if (timeRemaining > 0L && timeRemaining <= 10000L)
14212       nominalTickLength = 100L;
14213     else
14214       nominalTickLength = 1000L;
14215     nextTickLength = timeRemaining % nominalTickLength;
14216     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14217
14218     return nextTickLength;
14219 }
14220
14221 /* Adjust clock one minute up or down */
14222 void
14223 AdjustClock(Boolean which, int dir)
14224 {
14225     if(which) blackTimeRemaining += 60000*dir;
14226     else      whiteTimeRemaining += 60000*dir;
14227     DisplayBothClocks();
14228 }
14229
14230 /* Stop clocks and reset to a fresh time control */
14231 void
14232 ResetClocks() 
14233 {
14234     (void) StopClockTimer();
14235     if (appData.icsActive) {
14236         whiteTimeRemaining = blackTimeRemaining = 0;
14237     } else if (searchTime) {
14238         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14239         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14240     } else { /* [HGM] correct new time quote for time odds */
14241         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14242         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14243     }
14244     if (whiteFlag || blackFlag) {
14245         DisplayTitle("");
14246         whiteFlag = blackFlag = FALSE;
14247     }
14248     DisplayBothClocks();
14249 }
14250
14251 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14252
14253 /* Decrement running clock by amount of time that has passed */
14254 void
14255 DecrementClocks()
14256 {
14257     long timeRemaining;
14258     long lastTickLength, fudge;
14259     TimeMark now;
14260
14261     if (!appData.clockMode) return;
14262     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14263         
14264     GetTimeMark(&now);
14265
14266     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14267
14268     /* Fudge if we woke up a little too soon */
14269     fudge = intendedTickLength - lastTickLength;
14270     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14271
14272     if (WhiteOnMove(forwardMostMove)) {
14273         if(whiteNPS >= 0) lastTickLength = 0;
14274         timeRemaining = whiteTimeRemaining -= lastTickLength;
14275         DisplayWhiteClock(whiteTimeRemaining - fudge,
14276                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14277     } else {
14278         if(blackNPS >= 0) lastTickLength = 0;
14279         timeRemaining = blackTimeRemaining -= lastTickLength;
14280         DisplayBlackClock(blackTimeRemaining - fudge,
14281                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14282     }
14283
14284     if (CheckFlags()) return;
14285         
14286     tickStartTM = now;
14287     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14288     StartClockTimer(intendedTickLength);
14289
14290     /* if the time remaining has fallen below the alarm threshold, sound the
14291      * alarm. if the alarm has sounded and (due to a takeback or time control
14292      * with increment) the time remaining has increased to a level above the
14293      * threshold, reset the alarm so it can sound again. 
14294      */
14295     
14296     if (appData.icsActive && appData.icsAlarm) {
14297
14298         /* make sure we are dealing with the user's clock */
14299         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14300                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14301            )) return;
14302
14303         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14304             alarmSounded = FALSE;
14305         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14306             PlayAlarmSound();
14307             alarmSounded = TRUE;
14308         }
14309     }
14310 }
14311
14312
14313 /* A player has just moved, so stop the previously running
14314    clock and (if in clock mode) start the other one.
14315    We redisplay both clocks in case we're in ICS mode, because
14316    ICS gives us an update to both clocks after every move.
14317    Note that this routine is called *after* forwardMostMove
14318    is updated, so the last fractional tick must be subtracted
14319    from the color that is *not* on move now.
14320 */
14321 void
14322 SwitchClocks(int newMoveNr)
14323 {
14324     long lastTickLength;
14325     TimeMark now;
14326     int flagged = FALSE;
14327
14328     GetTimeMark(&now);
14329
14330     if (StopClockTimer() && appData.clockMode) {
14331         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14332         if (!WhiteOnMove(forwardMostMove)) {
14333             if(blackNPS >= 0) lastTickLength = 0;
14334             blackTimeRemaining -= lastTickLength;
14335            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14336 //         if(pvInfoList[forwardMostMove-1].time == -1)
14337                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14338                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14339         } else {
14340            if(whiteNPS >= 0) lastTickLength = 0;
14341            whiteTimeRemaining -= lastTickLength;
14342            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14343 //         if(pvInfoList[forwardMostMove-1].time == -1)
14344                  pvInfoList[forwardMostMove-1].time = 
14345                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14346         }
14347         flagged = CheckFlags();
14348     }
14349     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14350     CheckTimeControl();
14351
14352     if (flagged || !appData.clockMode) return;
14353
14354     switch (gameMode) {
14355       case MachinePlaysBlack:
14356       case MachinePlaysWhite:
14357       case BeginningOfGame:
14358         if (pausing) return;
14359         break;
14360
14361       case EditGame:
14362       case PlayFromGameFile:
14363       case IcsExamining:
14364         return;
14365
14366       default:
14367         break;
14368     }
14369
14370     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14371         if(WhiteOnMove(forwardMostMove))
14372              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14373         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14374     }
14375
14376     tickStartTM = now;
14377     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14378       whiteTimeRemaining : blackTimeRemaining);
14379     StartClockTimer(intendedTickLength);
14380 }
14381         
14382
14383 /* Stop both clocks */
14384 void
14385 StopClocks()
14386 {       
14387     long lastTickLength;
14388     TimeMark now;
14389
14390     if (!StopClockTimer()) return;
14391     if (!appData.clockMode) return;
14392
14393     GetTimeMark(&now);
14394
14395     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14396     if (WhiteOnMove(forwardMostMove)) {
14397         if(whiteNPS >= 0) lastTickLength = 0;
14398         whiteTimeRemaining -= lastTickLength;
14399         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14400     } else {
14401         if(blackNPS >= 0) lastTickLength = 0;
14402         blackTimeRemaining -= lastTickLength;
14403         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14404     }
14405     CheckFlags();
14406 }
14407         
14408 /* Start clock of player on move.  Time may have been reset, so
14409    if clock is already running, stop and restart it. */
14410 void
14411 StartClocks()
14412 {
14413     (void) StopClockTimer(); /* in case it was running already */
14414     DisplayBothClocks();
14415     if (CheckFlags()) return;
14416
14417     if (!appData.clockMode) return;
14418     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14419
14420     GetTimeMark(&tickStartTM);
14421     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14422       whiteTimeRemaining : blackTimeRemaining);
14423
14424    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14425     whiteNPS = blackNPS = -1; 
14426     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14427        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14428         whiteNPS = first.nps;
14429     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14430        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14431         blackNPS = first.nps;
14432     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14433         whiteNPS = second.nps;
14434     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14435         blackNPS = second.nps;
14436     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14437
14438     StartClockTimer(intendedTickLength);
14439 }
14440
14441 char *
14442 TimeString(ms)
14443      long ms;
14444 {
14445     long second, minute, hour, day;
14446     char *sign = "";
14447     static char buf[32];
14448     
14449     if (ms > 0 && ms <= 9900) {
14450       /* convert milliseconds to tenths, rounding up */
14451       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14452
14453       sprintf(buf, " %03.1f ", tenths/10.0);
14454       return buf;
14455     }
14456
14457     /* convert milliseconds to seconds, rounding up */
14458     /* use floating point to avoid strangeness of integer division
14459        with negative dividends on many machines */
14460     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14461
14462     if (second < 0) {
14463         sign = "-";
14464         second = -second;
14465     }
14466     
14467     day = second / (60 * 60 * 24);
14468     second = second % (60 * 60 * 24);
14469     hour = second / (60 * 60);
14470     second = second % (60 * 60);
14471     minute = second / 60;
14472     second = second % 60;
14473     
14474     if (day > 0)
14475       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14476               sign, day, hour, minute, second);
14477     else if (hour > 0)
14478       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14479     else
14480       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14481     
14482     return buf;
14483 }
14484
14485
14486 /*
14487  * This is necessary because some C libraries aren't ANSI C compliant yet.
14488  */
14489 char *
14490 StrStr(string, match)
14491      char *string, *match;
14492 {
14493     int i, length;
14494     
14495     length = strlen(match);
14496     
14497     for (i = strlen(string) - length; i >= 0; i--, string++)
14498       if (!strncmp(match, string, length))
14499         return string;
14500     
14501     return NULL;
14502 }
14503
14504 char *
14505 StrCaseStr(string, match)
14506      char *string, *match;
14507 {
14508     int i, j, length;
14509     
14510     length = strlen(match);
14511     
14512     for (i = strlen(string) - length; i >= 0; i--, string++) {
14513         for (j = 0; j < length; j++) {
14514             if (ToLower(match[j]) != ToLower(string[j]))
14515               break;
14516         }
14517         if (j == length) return string;
14518     }
14519
14520     return NULL;
14521 }
14522
14523 #ifndef _amigados
14524 int
14525 StrCaseCmp(s1, s2)
14526      char *s1, *s2;
14527 {
14528     char c1, c2;
14529     
14530     for (;;) {
14531         c1 = ToLower(*s1++);
14532         c2 = ToLower(*s2++);
14533         if (c1 > c2) return 1;
14534         if (c1 < c2) return -1;
14535         if (c1 == NULLCHAR) return 0;
14536     }
14537 }
14538
14539
14540 int
14541 ToLower(c)
14542      int c;
14543 {
14544     return isupper(c) ? tolower(c) : c;
14545 }
14546
14547
14548 int
14549 ToUpper(c)
14550      int c;
14551 {
14552     return islower(c) ? toupper(c) : c;
14553 }
14554 #endif /* !_amigados    */
14555
14556 char *
14557 StrSave(s)
14558      char *s;
14559 {
14560     char *ret;
14561
14562     if ((ret = (char *) malloc(strlen(s) + 1))) {
14563         strcpy(ret, s);
14564     }
14565     return ret;
14566 }
14567
14568 char *
14569 StrSavePtr(s, savePtr)
14570      char *s, **savePtr;
14571 {
14572     if (*savePtr) {
14573         free(*savePtr);
14574     }
14575     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14576         strcpy(*savePtr, s);
14577     }
14578     return(*savePtr);
14579 }
14580
14581 char *
14582 PGNDate()
14583 {
14584     time_t clock;
14585     struct tm *tm;
14586     char buf[MSG_SIZ];
14587
14588     clock = time((time_t *)NULL);
14589     tm = localtime(&clock);
14590     sprintf(buf, "%04d.%02d.%02d",
14591             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14592     return StrSave(buf);
14593 }
14594
14595
14596 char *
14597 PositionToFEN(move, overrideCastling)
14598      int move;
14599      char *overrideCastling;
14600 {
14601     int i, j, fromX, fromY, toX, toY;
14602     int whiteToPlay;
14603     char buf[128];
14604     char *p, *q;
14605     int emptycount;
14606     ChessSquare piece;
14607
14608     whiteToPlay = (gameMode == EditPosition) ?
14609       !blackPlaysFirst : (move % 2 == 0);
14610     p = buf;
14611
14612     /* Piece placement data */
14613     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14614         emptycount = 0;
14615         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14616             if (boards[move][i][j] == EmptySquare) {
14617                 emptycount++;
14618             } else { ChessSquare piece = boards[move][i][j];
14619                 if (emptycount > 0) {
14620                     if(emptycount<10) /* [HGM] can be >= 10 */
14621                         *p++ = '0' + emptycount;
14622                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14623                     emptycount = 0;
14624                 }
14625                 if(PieceToChar(piece) == '+') {
14626                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14627                     *p++ = '+';
14628                     piece = (ChessSquare)(DEMOTED piece);
14629                 } 
14630                 *p++ = PieceToChar(piece);
14631                 if(p[-1] == '~') {
14632                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14633                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14634                     *p++ = '~';
14635                 }
14636             }
14637         }
14638         if (emptycount > 0) {
14639             if(emptycount<10) /* [HGM] can be >= 10 */
14640                 *p++ = '0' + emptycount;
14641             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14642             emptycount = 0;
14643         }
14644         *p++ = '/';
14645     }
14646     *(p - 1) = ' ';
14647
14648     /* [HGM] print Crazyhouse or Shogi holdings */
14649     if( gameInfo.holdingsWidth ) {
14650         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14651         q = p;
14652         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14653             piece = boards[move][i][BOARD_WIDTH-1];
14654             if( piece != EmptySquare )
14655               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14656                   *p++ = PieceToChar(piece);
14657         }
14658         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14659             piece = boards[move][BOARD_HEIGHT-i-1][0];
14660             if( piece != EmptySquare )
14661               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14662                   *p++ = PieceToChar(piece);
14663         }
14664
14665         if( q == p ) *p++ = '-';
14666         *p++ = ']';
14667         *p++ = ' ';
14668     }
14669
14670     /* Active color */
14671     *p++ = whiteToPlay ? 'w' : 'b';
14672     *p++ = ' ';
14673
14674   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14675     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14676   } else {
14677   if(nrCastlingRights) {
14678      q = p;
14679      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14680        /* [HGM] write directly from rights */
14681            if(boards[move][CASTLING][2] != NoRights &&
14682               boards[move][CASTLING][0] != NoRights   )
14683                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14684            if(boards[move][CASTLING][2] != NoRights &&
14685               boards[move][CASTLING][1] != NoRights   )
14686                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14687            if(boards[move][CASTLING][5] != NoRights &&
14688               boards[move][CASTLING][3] != NoRights   )
14689                 *p++ = boards[move][CASTLING][3] + AAA;
14690            if(boards[move][CASTLING][5] != NoRights &&
14691               boards[move][CASTLING][4] != NoRights   )
14692                 *p++ = boards[move][CASTLING][4] + AAA;
14693      } else {
14694
14695         /* [HGM] write true castling rights */
14696         if( nrCastlingRights == 6 ) {
14697             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14698                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14699             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14700                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14701             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14702                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14703             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14704                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14705         }
14706      }
14707      if (q == p) *p++ = '-'; /* No castling rights */
14708      *p++ = ' ';
14709   }
14710
14711   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14712      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14713     /* En passant target square */
14714     if (move > backwardMostMove) {
14715         fromX = moveList[move - 1][0] - AAA;
14716         fromY = moveList[move - 1][1] - ONE;
14717         toX = moveList[move - 1][2] - AAA;
14718         toY = moveList[move - 1][3] - ONE;
14719         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14720             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14721             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14722             fromX == toX) {
14723             /* 2-square pawn move just happened */
14724             *p++ = toX + AAA;
14725             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14726         } else {
14727             *p++ = '-';
14728         }
14729     } else if(move == backwardMostMove) {
14730         // [HGM] perhaps we should always do it like this, and forget the above?
14731         if((signed char)boards[move][EP_STATUS] >= 0) {
14732             *p++ = boards[move][EP_STATUS] + AAA;
14733             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14734         } else {
14735             *p++ = '-';
14736         }
14737     } else {
14738         *p++ = '-';
14739     }
14740     *p++ = ' ';
14741   }
14742   }
14743
14744     /* [HGM] find reversible plies */
14745     {   int i = 0, j=move;
14746
14747         if (appData.debugMode) { int k;
14748             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14749             for(k=backwardMostMove; k<=forwardMostMove; k++)
14750                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14751
14752         }
14753
14754         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14755         if( j == backwardMostMove ) i += initialRulePlies;
14756         sprintf(p, "%d ", i);
14757         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14758     }
14759     /* Fullmove number */
14760     sprintf(p, "%d", (move / 2) + 1);
14761     
14762     return StrSave(buf);
14763 }
14764
14765 Boolean
14766 ParseFEN(board, blackPlaysFirst, fen)
14767     Board board;
14768      int *blackPlaysFirst;
14769      char *fen;
14770 {
14771     int i, j;
14772     char *p, c;
14773     int emptycount;
14774     ChessSquare piece;
14775
14776     p = fen;
14777
14778     /* [HGM] by default clear Crazyhouse holdings, if present */
14779     if(gameInfo.holdingsWidth) {
14780        for(i=0; i<BOARD_HEIGHT; i++) {
14781            board[i][0]             = EmptySquare; /* black holdings */
14782            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14783            board[i][1]             = (ChessSquare) 0; /* black counts */
14784            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14785        }
14786     }
14787
14788     /* Piece placement data */
14789     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14790         j = 0;
14791         for (;;) {
14792             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14793                 if (*p == '/') p++;
14794                 emptycount = gameInfo.boardWidth - j;
14795                 while (emptycount--)
14796                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14797                 break;
14798 #if(BOARD_FILES >= 10)
14799             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14800                 p++; emptycount=10;
14801                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14802                 while (emptycount--)
14803                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14804 #endif
14805             } else if (isdigit(*p)) {
14806                 emptycount = *p++ - '0';
14807                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14808                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14809                 while (emptycount--)
14810                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14811             } else if (*p == '+' || isalpha(*p)) {
14812                 if (j >= gameInfo.boardWidth) return FALSE;
14813                 if(*p=='+') {
14814                     piece = CharToPiece(*++p);
14815                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14816                     piece = (ChessSquare) (PROMOTED piece ); p++;
14817                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14818                 } else piece = CharToPiece(*p++);
14819
14820                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14821                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14822                     piece = (ChessSquare) (PROMOTED piece);
14823                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14824                     p++;
14825                 }
14826                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14827             } else {
14828                 return FALSE;
14829             }
14830         }
14831     }
14832     while (*p == '/' || *p == ' ') p++;
14833
14834     /* [HGM] look for Crazyhouse holdings here */
14835     while(*p==' ') p++;
14836     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14837         if(*p == '[') p++;
14838         if(*p == '-' ) *p++; /* empty holdings */ else {
14839             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14840             /* if we would allow FEN reading to set board size, we would   */
14841             /* have to add holdings and shift the board read so far here   */
14842             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14843                 *p++;
14844                 if((int) piece >= (int) BlackPawn ) {
14845                     i = (int)piece - (int)BlackPawn;
14846                     i = PieceToNumber((ChessSquare)i);
14847                     if( i >= gameInfo.holdingsSize ) return FALSE;
14848                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14849                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14850                 } else {
14851                     i = (int)piece - (int)WhitePawn;
14852                     i = PieceToNumber((ChessSquare)i);
14853                     if( i >= gameInfo.holdingsSize ) return FALSE;
14854                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14855                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14856                 }
14857             }
14858         }
14859         if(*p == ']') *p++;
14860     }
14861
14862     while(*p == ' ') p++;
14863
14864     /* Active color */
14865     c = *p++;
14866     if(appData.colorNickNames) {
14867       if( c == appData.colorNickNames[0] ) c = 'w'; else
14868       if( c == appData.colorNickNames[1] ) c = 'b';
14869     }
14870     switch (c) {
14871       case 'w':
14872         *blackPlaysFirst = FALSE;
14873         break;
14874       case 'b': 
14875         *blackPlaysFirst = TRUE;
14876         break;
14877       default:
14878         return FALSE;
14879     }
14880
14881     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14882     /* return the extra info in global variiables             */
14883
14884     /* set defaults in case FEN is incomplete */
14885     board[EP_STATUS] = EP_UNKNOWN;
14886     for(i=0; i<nrCastlingRights; i++ ) {
14887         board[CASTLING][i] =
14888             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14889     }   /* assume possible unless obviously impossible */
14890     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14891     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14892     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14893                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14894     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14895     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14896     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14897                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14898     FENrulePlies = 0;
14899
14900     while(*p==' ') p++;
14901     if(nrCastlingRights) {
14902       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14903           /* castling indicator present, so default becomes no castlings */
14904           for(i=0; i<nrCastlingRights; i++ ) {
14905                  board[CASTLING][i] = NoRights;
14906           }
14907       }
14908       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14909              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14910              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14911              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14912         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14913
14914         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14915             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14916             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14917         }
14918         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14919             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14920         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14921                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14922         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14923                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14924         switch(c) {
14925           case'K':
14926               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14927               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14928               board[CASTLING][2] = whiteKingFile;
14929               break;
14930           case'Q':
14931               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14932               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14933               board[CASTLING][2] = whiteKingFile;
14934               break;
14935           case'k':
14936               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14937               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14938               board[CASTLING][5] = blackKingFile;
14939               break;
14940           case'q':
14941               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14942               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14943               board[CASTLING][5] = blackKingFile;
14944           case '-':
14945               break;
14946           default: /* FRC castlings */
14947               if(c >= 'a') { /* black rights */
14948                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14949                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14950                   if(i == BOARD_RGHT) break;
14951                   board[CASTLING][5] = i;
14952                   c -= AAA;
14953                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14954                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14955                   if(c > i)
14956                       board[CASTLING][3] = c;
14957                   else
14958                       board[CASTLING][4] = c;
14959               } else { /* white rights */
14960                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14961                     if(board[0][i] == WhiteKing) break;
14962                   if(i == BOARD_RGHT) break;
14963                   board[CASTLING][2] = i;
14964                   c -= AAA - 'a' + 'A';
14965                   if(board[0][c] >= WhiteKing) break;
14966                   if(c > i)
14967                       board[CASTLING][0] = c;
14968                   else
14969                       board[CASTLING][1] = c;
14970               }
14971         }
14972       }
14973       for(i=0; i<nrCastlingRights; i++)
14974         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14975     if (appData.debugMode) {
14976         fprintf(debugFP, "FEN castling rights:");
14977         for(i=0; i<nrCastlingRights; i++)
14978         fprintf(debugFP, " %d", board[CASTLING][i]);
14979         fprintf(debugFP, "\n");
14980     }
14981
14982       while(*p==' ') p++;
14983     }
14984
14985     /* read e.p. field in games that know e.p. capture */
14986     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14987        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14988       if(*p=='-') {
14989         p++; board[EP_STATUS] = EP_NONE;
14990       } else {
14991          char c = *p++ - AAA;
14992
14993          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14994          if(*p >= '0' && *p <='9') *p++;
14995          board[EP_STATUS] = c;
14996       }
14997     }
14998
14999
15000     if(sscanf(p, "%d", &i) == 1) {
15001         FENrulePlies = i; /* 50-move ply counter */
15002         /* (The move number is still ignored)    */
15003     }
15004
15005     return TRUE;
15006 }
15007       
15008 void
15009 EditPositionPasteFEN(char *fen)
15010 {
15011   if (fen != NULL) {
15012     Board initial_position;
15013
15014     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15015       DisplayError(_("Bad FEN position in clipboard"), 0);
15016       return ;
15017     } else {
15018       int savedBlackPlaysFirst = blackPlaysFirst;
15019       EditPositionEvent();
15020       blackPlaysFirst = savedBlackPlaysFirst;
15021       CopyBoard(boards[0], initial_position);
15022       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15023       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15024       DisplayBothClocks();
15025       DrawPosition(FALSE, boards[currentMove]);
15026     }
15027   }
15028 }
15029
15030 static char cseq[12] = "\\   ";
15031
15032 Boolean set_cont_sequence(char *new_seq)
15033 {
15034     int len;
15035     Boolean ret;
15036
15037     // handle bad attempts to set the sequence
15038         if (!new_seq)
15039                 return 0; // acceptable error - no debug
15040
15041     len = strlen(new_seq);
15042     ret = (len > 0) && (len < sizeof(cseq));
15043     if (ret)
15044         strcpy(cseq, new_seq);
15045     else if (appData.debugMode)
15046         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15047     return ret;
15048 }
15049
15050 /*
15051     reformat a source message so words don't cross the width boundary.  internal
15052     newlines are not removed.  returns the wrapped size (no null character unless
15053     included in source message).  If dest is NULL, only calculate the size required
15054     for the dest buffer.  lp argument indicats line position upon entry, and it's
15055     passed back upon exit.
15056 */
15057 int wrap(char *dest, char *src, int count, int width, int *lp)
15058 {
15059     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15060
15061     cseq_len = strlen(cseq);
15062     old_line = line = *lp;
15063     ansi = len = clen = 0;
15064
15065     for (i=0; i < count; i++)
15066     {
15067         if (src[i] == '\033')
15068             ansi = 1;
15069
15070         // if we hit the width, back up
15071         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15072         {
15073             // store i & len in case the word is too long
15074             old_i = i, old_len = len;
15075
15076             // find the end of the last word
15077             while (i && src[i] != ' ' && src[i] != '\n')
15078             {
15079                 i--;
15080                 len--;
15081             }
15082
15083             // word too long?  restore i & len before splitting it
15084             if ((old_i-i+clen) >= width)
15085             {
15086                 i = old_i;
15087                 len = old_len;
15088             }
15089
15090             // extra space?
15091             if (i && src[i-1] == ' ')
15092                 len--;
15093
15094             if (src[i] != ' ' && src[i] != '\n')
15095             {
15096                 i--;
15097                 if (len)
15098                     len--;
15099             }
15100
15101             // now append the newline and continuation sequence
15102             if (dest)
15103                 dest[len] = '\n';
15104             len++;
15105             if (dest)
15106                 strncpy(dest+len, cseq, cseq_len);
15107             len += cseq_len;
15108             line = cseq_len;
15109             clen = cseq_len;
15110             continue;
15111         }
15112
15113         if (dest)
15114             dest[len] = src[i];
15115         len++;
15116         if (!ansi)
15117             line++;
15118         if (src[i] == '\n')
15119             line = 0;
15120         if (src[i] == 'm')
15121             ansi = 0;
15122     }
15123     if (dest && appData.debugMode)
15124     {
15125         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15126             count, width, line, len, *lp);
15127         show_bytes(debugFP, src, count);
15128         fprintf(debugFP, "\ndest: ");
15129         show_bytes(debugFP, dest, len);
15130         fprintf(debugFP, "\n");
15131     }
15132     *lp = dest ? line : old_line;
15133
15134     return len;
15135 }
15136
15137 // [HGM] vari: routines for shelving variations
15138
15139 void 
15140 PushTail(int firstMove, int lastMove)
15141 {
15142         int i, j, nrMoves = lastMove - firstMove;
15143
15144         if(appData.icsActive) { // only in local mode
15145                 forwardMostMove = currentMove; // mimic old ICS behavior
15146                 return;
15147         }
15148         if(storedGames >= MAX_VARIATIONS-1) return;
15149
15150         // push current tail of game on stack
15151         savedResult[storedGames] = gameInfo.result;
15152         savedDetails[storedGames] = gameInfo.resultDetails;
15153         gameInfo.resultDetails = NULL;
15154         savedFirst[storedGames] = firstMove;
15155         savedLast [storedGames] = lastMove;
15156         savedFramePtr[storedGames] = framePtr;
15157         framePtr -= nrMoves; // reserve space for the boards
15158         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15159             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15160             for(j=0; j<MOVE_LEN; j++)
15161                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15162             for(j=0; j<2*MOVE_LEN; j++)
15163                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15164             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15165             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15166             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15167             pvInfoList[firstMove+i-1].depth = 0;
15168             commentList[framePtr+i] = commentList[firstMove+i];
15169             commentList[firstMove+i] = NULL;
15170         }
15171
15172         storedGames++;
15173         forwardMostMove = firstMove; // truncate game so we can start variation
15174         if(storedGames == 1) GreyRevert(FALSE);
15175 }
15176
15177 Boolean
15178 PopTail(Boolean annotate)
15179 {
15180         int i, j, nrMoves;
15181         char buf[8000], moveBuf[20];
15182
15183         if(appData.icsActive) return FALSE; // only in local mode
15184         if(!storedGames) return FALSE; // sanity
15185         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15186
15187         storedGames--;
15188         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15189         nrMoves = savedLast[storedGames] - currentMove;
15190         if(annotate) {
15191                 int cnt = 10;
15192                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15193                 else strcpy(buf, "(");
15194                 for(i=currentMove; i<forwardMostMove; i++) {
15195                         if(WhiteOnMove(i))
15196                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15197                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15198                         strcat(buf, moveBuf);
15199                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15200                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15201                 }
15202                 strcat(buf, ")");
15203         }
15204         for(i=1; i<=nrMoves; i++) { // copy last variation back
15205             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15206             for(j=0; j<MOVE_LEN; j++)
15207                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15208             for(j=0; j<2*MOVE_LEN; j++)
15209                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15210             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15211             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15212             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15213             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15214             commentList[currentMove+i] = commentList[framePtr+i];
15215             commentList[framePtr+i] = NULL;
15216         }
15217         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15218         framePtr = savedFramePtr[storedGames];
15219         gameInfo.result = savedResult[storedGames];
15220         if(gameInfo.resultDetails != NULL) {
15221             free(gameInfo.resultDetails);
15222       }
15223         gameInfo.resultDetails = savedDetails[storedGames];
15224         forwardMostMove = currentMove + nrMoves;
15225         if(storedGames == 0) GreyRevert(TRUE);
15226         return TRUE;
15227 }
15228
15229 void 
15230 CleanupTail()
15231 {       // remove all shelved variations
15232         int i;
15233         for(i=0; i<storedGames; i++) {
15234             if(savedDetails[i])
15235                 free(savedDetails[i]);
15236             savedDetails[i] = NULL;
15237         }
15238         for(i=framePtr; i<MAX_MOVES; i++) {
15239                 if(commentList[i]) free(commentList[i]);
15240                 commentList[i] = NULL;
15241         }
15242         framePtr = MAX_MOVES-1;
15243         storedGames = 0;
15244 }
15245
15246 void
15247 LoadVariation(int index, char *text)
15248 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15249         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15250         int level = 0, move;
15251
15252         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15253         // first find outermost bracketing variation
15254         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15255             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15256                 if(*p == '{') wait = '}'; else
15257                 if(*p == '[') wait = ']'; else
15258                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15259                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15260             }
15261             if(*p == wait) wait = NULLCHAR; // closing ]} found
15262             p++;
15263         }
15264         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15265         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15266         end[1] = NULLCHAR; // clip off comment beyond variation
15267         ToNrEvent(currentMove-1);
15268         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15269         // kludge: use ParsePV() to append variation to game
15270         move = currentMove;
15271         ParsePV(start, TRUE);
15272         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15273         ClearPremoveHighlights();
15274         CommentPopDown();
15275         ToNrEvent(currentMove+1);
15276 }
15277